Как ПостроительЗапроса преобразует имена колонок Excel с пробелами при загрузке данных в 1С?

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

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

Автоматическое преобразование имен колонок: механизм работы ПостроителяЗапроса

Когда вы используете ПостроительЗапроса для выгрузки данных из ТабличногоДокумента, платформа 1С автоматически пытается привести имена колонок, взятые из заголовков Excel, к виду, допустимому для имен полей ТаблицыЗначений или реквизитов объектов 1С. Это необходимо, потому что имена полей в 1С должны соответствовать правилам именования переменных и не могут содержать такие символы, как пробелы, скобки, дефисы и другие спецсимволы.

Ключевым моментом в этом процессе является метод ПЗ.ЗаполнитьНастройки() объекта ПостроительЗапроса. Этот метод, после того как ему был указан источник данных (в нашем случае – Новый ОписаниеИсточникаДанных(ТД.Область("Область"))), самостоятельно анализирует структуру заголовков и формирует список полей для выгрузки, присваивая им уже преобразованные, валидные имена. Вы не сможете перейти в код этого метода по F12, поскольку это внутренний механизм платформы.

Рассмотрим пример кода, который демонстрирует такую загрузку:


ТЗДанныеИзЭксель = Новый ТаблицаЗначений;
ТД = Новый ТабличныйДокумент;
ТД.Прочитать(ИмяФайлаЭксель); // ИмяФайлаЭксель - путь к файлу Excel

// Предполагаем, что функция ТаблицаДанныхИзЭксель описана следующим образом:
// ТЗДанныеИзЭксель = ТаблицаДанныхИзЭксель(ТД);

// Описание функции ТаблицаДанныхИзЭксель, как в исходном сообщении:
Функция ТаблицаДанныхИзЭксель(ТД, Лист = "") // Добавляем необязательный параметр Лист
    // Если Лист не указан, или пуст, ПостроительЗапроса будет работать со всем документом
    // Если требуется конкретный лист, то его можно указать, но в данном примере используется Область("Область")
    ПЗ = Новый ПостроительЗапроса;
    // Источником данных является область ТабличногоДокумента
    ПЗ.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТД.Область("Область"));
    // Эта настройка указывает, что не нужно добавлять представления для полей,
    // что важно для формирования чистых имен колонок.
    ПЗ.ДобавлениеПредставлений = ТипДобавленияПредставлений.НеДобавлять;
    // Метод ЗаполнитьНастройки автоматически анализирует источник данных
    // и формирует список полей с преобразованными именами
    ПЗ.ЗаполнитьНастройки;
    ПЗ.Выполнить;
    // Результат запроса выгружается в ТаблицуЗначений
    Возврат ПЗ.Результат.Выгрузить();
КонецФункции

В этом коде вся "магия" преобразования происходит внутри вызова ПЗ.ЗаполнитьНастройки(). После его выполнения объект ПЗ уже содержит настроенный запрос с автоматически сформированными именами полей, которые затем будут использованы при выгрузке в ТаблицуЗначений.

Логика преобразования платформы 1С

Общие принципы преобразования имен колонок в 1С, когда вы используете ПостроительЗапроса для данных из Excel, включают следующее:

  1. Замена пробелов и специальных символов: Пробелы, скобки, дефисы и другие символы, недопустимые в именах полей 1С, чаще всего либо заменяются на подчеркивания (_), либо удаляются.
    • Например, колонка "Сроки для (для клиента) и не клиента" может быть преобразована в "Сроки_для_клиента_и_не_клиента". Здесь мы видим замену пробелов и скобок на подчеркивания.
    • Колонка "База для расчета" может быть преобразована в "БазаДляРасчета" (CamelCase) или "База_для_расчета". Это зависит от конкретного алгоритма платформы и типа символов.
  2. Регистр букв: Часто первые буквы слов, следующих за преобразованными пробелами, могут становиться заглавными (например, "База для расчета" в "БазаДляРасчета"), что соответствует общепринятому стилю именования переменных в 1С.
  3. Удаление недопустимых символов: Некоторые символы могут быть просто удалены, если они не являются разделителями или не имеют явной замены.
  4. Визуально неотличимые символы: Важным аспектом, влияющим на вариативность преобразования, может быть наличие разных типов пробелов или других визуально похожих символов в самом файле Excel. Например, обычный пробел (ASCII 32) и неразрывный пробел (ASCII 160) могут обрабатываться платформой по-разному, что приводит к различным результатам преобразования. Мы рекомендуем всегда проверять исходные данные в Excel на наличие таких "скрытых" символов.

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

Как получить контроль над именованием колонок?

Если автоматическое преобразование имен колонок вас не устраивает или приводит к непредсказуемым результатам, у нас есть несколько способов полностью контролировать имена колонок в результирующей ТаблицеЗначений:

1. Ручное создание колонок и заполнение по индексу

Этот подход предполагает, что мы сами программно создаем ТаблицуЗначений с нужными нам именами колонок и затем построчно заполняем ее данными, обращаясь к ячейкам ТабличногоДокумента по их индексам.

Порядок действий:

  1. Загружаем Excel в ТабличныйДокумент, как и раньше.
  2. Создаем новую ТаблицуЗначений.
  3. Добавляем в ТаблицуЗначений колонки с теми именами, которые нам нужны, и указываем их тип.
  4. В цикле построчно обходим ТабличныйДокумент (начиная со второй строки, если первая строка содержит заголовки).
  5. Для каждой строки создаем новую строку в ТаблицеЗначений.
  6. Заполняем поля этой строки данными из соответствующих ячеек ТабличногоДокумента, используя их индексы.

Пример кода:


Функция ЗагрузитьДанныеИзЭксельВручную(ИмяФайлаЭксель)
    // 1. Загружаем Excel в ТабличныйДокумент
    ТД = Новый ТабличныйДокумент;
    Попытка
        ТД.Прочитать(ИмяФайлаЭксель);
    Исключение
        Сообщить("Ошибка при чтении файла Excel: " + ОписаниеОшибки(), СтатусСообщения.Важное);
        Возврат Неопределено;
    КонецПопытки;

    ТЗРезультат = Новый ТаблицаЗначений;

    // 2. Определяем заголовки и их преобразованные имена, а также типы
    // Этот список можно вынести в настройки или сделать интерактивным для пользователя
    СоответствиеЗаголовков = Новый Соответствие;
    СоответствиеЗаголовков.Вставить("Сроки для (для клиента) и не клиента", "СрокиДляКлиента");
    СоответствиеЗаголовков.Вставить("База для расчета", "БазаДляРасчета");
    СоответствиеЗаголовков.Вставить("Наименование продукта", "НаименованиеПродукта");
    // Добавьте сюда все необходимые колонки из Excel и их желаемые имена в 1С

    КартаКолонок = Новый Соответствие; // Для сопоставления индексов с желаемыми именами

    // Получаем первую строку (заголовки) для определения индексов
    Если ТД.ВысотаТаблицы > 0 Тогда
        СтрокаЗаголовков = ТД.Область(1, 1, 1, ТД.ШиринаТаблицы); // Первая строка
        Для Каждого КолонкаТД Из СтрокаЗаголовков.Области Цикл
            ИмяЗаголовкаЭксель = СокрЛП(КолонкаТД.Текст); // Получаем текст заголовка
            
            Если СоответствиеЗаголовков.ЕстьКлюч(ИмяЗаголовкаЭксель) Тогда
                ЖелаемоеИмяКолонки = СоответствиеЗаголовков.Получить(ИмяЗаголовкаЭксель);
                Попытка
                    ТЗРезультат.Колонки.Добавить(ЖелаемоеИмяКолонки); // Добавляем колонку в ТаблицуЗначений
                    КартаКолонок.Вставить(КолонкаТД.ЛеваяКолонка, ЖелаемоеИмяКолонки); // Запоминаем индекс и имя
                Исключение
                    Сообщить("Не удалось добавить колонку '" + ЖелаемоеИмяКолонки + "'. Возможно, колонка с таким именем уже существует или имя некорректно.", СтатусСообщения.Внимание);
                КонецПопытки;
            Иначе
                Сообщить("Заголовок '" + ИмяЗаголовкаЭксель + "' из Excel не найден в сопоставлении. Колонка будет пропущена.", СтатусСообщения.Информация);
            КонецЕсли;
        КонецЦикла;
    Иначе
        Сообщить("Файл Excel пуст или не содержит данных.", СтатусСообщения.Внимание);
        Возврат Неопределено;
    КонецЕсли;

    // 3. Заполняем ТаблицуЗначений данными, начиная со второй строки Excel
    Для НомерСтроки = 2 По ТД.ВысотаТаблицы Цикл
        НоваяСтрокаТЗ = ТЗРезультат.Добавить();
        Для Каждого КолонкаТД Из ТД.Область(НомерСтроки, 1, НомерСтроки, ТД.ШиринаТаблицы).Области Цикл
            // Используем индекс колонки Excel для поиска соответствующего имени в ТЗ
            Если КартаКолонок.ЕстьКлюч(КолонкаТД.ЛеваяКолонка) Тогда
                ИмяКолонкиВТЗ = КартаКолонок.Получить(КолонкаТД.ЛеваяКолонка);
                // Пытаемся преобразовать значение к нужному типу, если тип колонки определен
                ЗначениеЯчейки = КолонкаТД.Значение;
                // Здесь можно добавить более сложную логику преобразования типов,
                // если ТЗРезультат.Колонки[ИмяКолонкиВТЗ].ТипЗначения определен
                Попытка
                    НоваяСтрокаТЗ[ИмяКолонкиВТЗ] = ЗначениеЯчейки;
                Исключение
                    Сообщить("Ошибка присвоения значения '" + ЗначениеЯчейки + "' колонке '" + ИмяКолонкиВТЗ + "': " + ОписаниеОшибки(), СтатусСообщения.Важное);
                КонецПопытки;
            КонецЕсли;
        КонецЦикла;
    КонецЦикла;

    Возврат ТЗРезультат;
КонецФункции

Этот подход предоставляет максимальный контроль, но требует больше ручной работы по сопоставлению и обработке данных.

2. Создание механизма сопоставления колонок

Для более гибкого решения мы можем создать механизм, который позволяет пользователю (или программно) явно указать, какая колонка Excel соответствует какому полю ТаблицыЗначений или реквизиту объекта 1С.

Порядок действий:

  1. Загружаем Excel в промежуточную ТаблицуЗначений, используя ПостроительЗапроса, как описано выше. Это позволит получить автоматически преобразованные имена колонок.
  2. Получаем список этих преобразованных имен колонок и список желаемых имен для конечной ТаблицыЗначений (или реквизитов объекта).
  3. Предоставляем пользователю интерфейс, где он сопоставляет колонки из промежуточной ТЗ с целевыми колонками (например, с помощью таблицы сопоставления).
  4. На основе этого сопоставления формируем финальную ТаблицуЗначений или заполняем объекты.

Пример концептуального сопоставления:


// Предположим, ТЗДанныеИзЭксель уже содержит данные с автоматически преобразованными именами
// из ПостроителяЗапроса (например, "Сроки_для_клиента_и_не_клиента", "БазаДляРасчета")

// Создадим таблицу соответствия, как если бы ее заполнил пользователь
ТаблицаСопоставления = Новый ТаблицаЗначений;
ТаблицаСопоставления.Колонки.Добавить("ИсходнаяКолонка"); // Имя колонки из ТЗДанныеИзЭксель
ТаблицаСопоставления.Колонки.Добавить("ЦелеваяКолонка");   // Желаемое имя в итоговой ТЗ

// Пример заполнения пользователем или программно
СтрокаСопоставления = ТаблицаСопоставления.Добавить();
СтрокаСопоставления.ИсходнаяКолонка = "Сроки_для_клиента_и_не_клиента";
СтрокаСопоставления.ЦелеваяКолонка   = "СрокИсполненияКлиент";

СтрокаСопоставления = ТаблицаСопоставления.Добавить();
СтрокаСопоставления.ИсходнаяКолонка = "БазаДляРасчета";
СтрокаСопоставления.ЦелеваяКолонка   = "БазаРасчета";

// Создаем итоговую ТаблицуЗначений с желаемыми именами
ТЗИтоговая = Новый ТаблицаЗначений;
Для Каждого СтрокаСопост Из ТаблицаСопоставления Цикл
    Если Не ТЗИтоговая.Колонки.Найти(СтрокаСопост.ЦелеваяКолонка) Тогда
        ТЗИтоговая.Колонки.Добавить(СтрокаСопост.ЦелеваяКолонка);
    КонецЕсли;
КонецЦикла;

// Заполняем итоговую ТаблицуЗначений
Для Каждого ИсходнаяСтрока Из ТЗДанныеИзЭксель Цикл
    НоваяСтрокаИтоговая = ТЗИтоговая.Добавить();
    Для Каждого СтрокаСопост Из ТаблицаСопоставления Цикл
        // Проверяем, существует ли исходная колонка в ТЗДанныеИзЭксель
        Если ТЗДанныеИзЭксель.Колонки.Найти(СтрокаСопост.ИсходнаяКолонка) Тогда
            НоваяСтрокаИтоговая[СтрокаСопост.ЦелеваяКолонка] = ИсходнаяСтрока[СтрокаСопост.ИсходнаяКолонка];
        Иначе
            Сообщить("Колонка '" + СтрокаСопост.ИсходнаяКолонка + "' не найдена в исходных данных.", СтатусСообщения.Внимание);
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Возврат ТЗИтоговая;

Этот метод является золотой серединой, так как он использует удобство автоматической загрузки, но позволяет скорректировать имена полей на втором этапе.

3. Предварительная очистка и нормализация заголовков Excel

Если у нас есть возможность перед загрузкой программно получить доступ к заголовкам Excel, мы можем попытаться "очистить" или "нормализовать" их, прежде чем передавать в ПостроительЗапроса. Это можно сделать, если мы сначала прочитаем только первую строку ТабличногоДокумента, вручную обработаем эти заголовки и затем либо используем их для ручного создания колонок (как в п.1), либо попытаемся скормить их ПостроителюЗапроса, если это возможно.

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

Пример функции нормализации строки:


Функция НормализоватьИмяКолонки(ИмяКолонкиИзЭксель)
    // Удаляем лишние пробелы в начале и конце
    ИмяКолонки = СокрЛП(ИмяКолонкиИзЭксель);
    
    // Заменяем все неразрывные пробелы на обычные, затем все обычные на подчеркивания
    ИмяКолонки = СтрЗаменить(ИмяКолонки, Символы.НП, " "); // Неразрывный пробел
    ИмяКолонки = СтрЗаменить(ИмяКолонки, Символы.Таб, " "); // Табуляция
    ИмяКолонки = СтрЗаменить(ИмяКолонки, " - ", "_"); // Некоторые комбинации с дефисом
    ИмяКолонки = СтрЗаменить(ИмяКолонки, " ", "_"); // Обычные пробелы
    
    // Удаляем скобки и их содержимое (если требуется)
    // Это более сложная логика, можно использовать регулярные выражения или циклы
    Пока Найти(ИмяКолонки, "(") > 0 И Найти(ИмяКолонки, ")") > 0 Цикл
        Начало = Найти(ИмяКолонки, "(");
        Конец   = Найти(ИмяКолонки, ")");
        Если Конец > Начало Тогда
            ИмяКолонки = Сред(ИмяКолонки, 1, Начало - 1) + Сред(ИмяКолонки, Конец + 1);
        Иначе
            Прервать;
        КонецЕсли;
    КонецЦикла;

    // Удаляем все символы, кроме букв, цифр и подчеркиваний
    // Это очень агрессивный метод, применять с осторожностью
    // ИмяКолонки = СтрЗаменить(ИмяКолонки, "/[^\w_]/g", ""); // Пример идеи, в 1С это более сложно

    // Убедимся, что нет множественных подчеркиваний подряд
    Пока Найти(ИмяКолонки, "__") > 0 Цикл
        ИмяКолонки = СтрЗаменить(ИмяКолонки, "__", "_");
    КонецЦикла;
    
    // Удаляем подчеркивание, если оно в конце или начале
    Если Лев(ИмяКолонки, 1) = "_" Тогда
        ИмяКолонки = Сред(ИмяКолонки, 2);
    КонецЕсли;
    Если Прав(ИмяКолонки, 1) = "_" Тогда
        ИмяКолонки = Лев(ИмяКолонки, СтрДлина(ИмяКолонки) - 1);
    КонецЕсли;
    
    Возврат ИмяКолонки;
КонецФункции

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

Заключение

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

← На главную