При разработке в среде «1С:Предприятие» часто возникает задача загрузки или обработки данных из табличных документов. Это может быть как макет, встроенный в конфигурацию (например, классификатор, тарифная сетка или заполненный шаблон), так и внешний файл Excel, загруженный в объект типа ТабличныйДокумент. Одним из самых популярных и лаконичных способов конвертации табличного документа в полноценную таблицу значений является использование механизма ПостроительОтчета или ПостроительЗапроса в сочетании с объектом ОписаниеИсточникаДанных. Этот способ называют «кодом в пять строк» за его простоту и быстродействие.
Однако при его использовании разработчики регулярно сталкиваются с крайне неприятной и неочевидной ошибкой: «Ожидается ключевое слово ВЫБРАТЬ» (или {(1, 1)}: Ожидается выражение "ВЫБРАТЬ"). Ситуация усложняется тем, что этот сбой происходит на этапе выполнения метода Выполнить(), хотя никакого явного SQL-запроса программист не пишет.
В этой статье мы подробно разберем природу этой ошибки, выясним, почему она возникает, как правильно настроить область макета для построителя, а также рассмотрим альтернативные и более надежные способы выгрузки данных, которые уберегут вас от подобных проблем в будущем.
Для того чтобы понять суть проблемы, нам необходимо заглянуть под капот платформы «1С:Предприятие». Когда мы инициализируем объект ОписаниеИсточникаДанных, передавая в него область табличного документа, платформа пытается автоматически распознать структуру этой таблицы.
Происходит следующий процесс:
ВЫБРАТЬ Поле1, Поле2 ИЗ Источник).Таким образом, корень проблемы кроется в неправильном определении границ области, которая передается в ОписаниеИсточникаДанных. Если в эту область попадают пустые строки над шапкой таблицы, система не сможет корректно проанализировать данные.
Рассмотрим правильный вариант реализации выгрузки. Главное правило — область табличного документа должна начинаться строго со строки, в которой расположены заголовки колонок таблицы.
Давайте разберем по шагам рабочий код, исправляющий ошибку автора темы на примере макета классификатора единиц измерения:
// 1. Получаем макет из справочника (или используем готовый ТабличныйДокумент)
Макет = Справочники.КлассификаторЕдиницИзмерения.ПолучитьМакет("КлассификаторЕдиницИзмерения");
// 2. Определяем точные координаты области данных.
// Предположим, что первая строка макета пустая или занята названием отчета,
// а реальные заголовки колонок (Код, Наименование, УсловноеОбозначение) находятся во 2-й строке.
СтрокаЗаголовка = 2;
// Задаем область: левый верхний угол (СтрокаЗаголовка, 1) и правый нижний (Высота, Ширина)
ОбластьДанных = Макет.Область(СтрокаЗаголовка, 1, Макет.ВысотаТаблицы, Макет.ШиринаТаблицы);
// 3. Создаем источник данных и инициализируем построитель
ПостроительОтчета = Новый ПостроительОтчета;
ПостроительОтчета.ИсточникДанных = Новый ОписаниеИсточникаДанных(ОбластьДанных);
// 4. Выполняем построение запроса и выгружаем результат в Таблицу Значений
ПостроительОтчета.Выполнить();
Тз = ПостроительОтчета.Результат.Выгрузить();
Обратите внимание на объект, который используется для выгрузки. В оригинальном коде автора изначально фигурировал ПостроительЗапроса. Хотя в некоторых старых версиях платформы или при определенных условиях это могло работать, штатным и наиболее стабильным инструментом для разбора табличных документов является именно ПостроительОтчета. Он специально спроектирован для анализа областей ячеек и генерации макетов вывода на их основе.
Используя данный метод, вы должны помнить о ряде ограничений и особенностей автоматического парсинга таблиц платформой:
ТаблицаЗначений будут созданы на основе текста из ячеек первой строки области. При этом платформа автоматически выполняет нормализацию имен, чтобы они соответствовали правилам именования переменных 1С (без пробелов, знаков препинания и спецсимволов). Например, заголовок «Код ОКЕИ» превратится в имя колонки Код_ОКЕИ, а «Вес, кг» — в Вес__кг. Платформа заменяет все недопустимые знаки на символ подчеркивания._1, _2 и так далее). Это может затруднить дальнейшее обращение к колонкам по именам.ПостроительОтчета и ОписаниеИсточникаДанных доступны только на сервере. Если вы разрабатываете конфигурацию на управляемых формах, этот код нельзя выполнять непосредственно в клиентских процедурах. Вам необходимо передать ваш ТабличныйДокумент на сервер (например, в функцию с директивой &НаСервереБезКонтекста), выполнить конвертацию там, а затем вернуть результат обратно.Несмотря на привлекательность автоматического метода с ПостроительОтчета, на практике он часто оказывается слишком хрупким. Если пользователи загружают файлы со сложной или нестабильной структурой, автоматический разбор может легко сломаться.
Для таких случаев гораздо надежнее написать классический ручной обход табличного документа. Это позволит нам самостоятельно управлять процессом создания колонок, очищать имена от лишних символов, контролировать типы данных и корректно обрабатывать пустые строки.
Проанализируем пример такого алгоритма, который работает как на клиенте, так и на сервере:
// Функция для безопасного преобразования любого текста в валидное имя колонки ТЗ
Функция ПолучитьБезопасноеИмяКолонки(ИсходныйТекст)
// Разрешенные символы для имен колонок в 1С
РазрешенныеСимволы = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"
+ "0123456789_";
Результат = "";
Для Поз = 1 По СтрДлина(ИсходныйТекст) Цикл
Символ = Сред(ИсходныйТекст, Поз, 1);
Если Найти(РазрешенныеСимволы, Символ) > 0 Тогда
// Имя колонки не должно начинаться с цифры
Если Поз = 1 И Найти("0123456789", Символ) > 0 Тогда
Результат = "_" + Символ;
Иначе
Результат = Результат + Символ;
КонецЕсли;
Иначе
Результат = Результат + "_";
КонецЕсли;
КонецЦикла;
Если ПустаяСтрока(Результат) Тогда
Результат = "ИмяКолонкиНеОпределено";
КонецЕсли;
Возврат Результат;
EndФункции
// Основная процедура конвертации табличного документа в ТЗ
Функция КонвертироватьТабДокВТЗ(ТабДок, СтрокаШапки = 1, СтрокаНачалаДанных = 2) Экспорт
ТЗ = Новый ТаблицаЗначений;
ШиринаТаблицы = ТабДок.ШиринаТаблицы;
ВысотаТаблицы = ТабДок.ВысотаТаблицы;
// 1. Формируем колонки таблицы значений на основе указанной строки шапки
Для НомерКолонки = 1 По ШиринаТаблицы Цикл
ТекстЯчейки = ТабДок.Область(СтрокаШапки, НомерКолонки).Текст;
ИмяКолонки = ПолучитьБезопасноеИмяКолонки(ТекстЯчейки);
// Защита от дублирования имен колонок
Счетчик = 1;
БазовоеИмя = ИмяКолонки;
Пока ТЗ.Колонки.Найти(ИмяКолонки) <> Неопределено Цикл
ИмяКолонки = БазовоеИмя + "_" + Счетчик;
Счетчик = Счетчик + 1;
КонецЦикла;
// Добавляем колонку, сохраняя оригинальный текст в качестве синонима
ТЗ.Колонки.Добавить(ИмяКолонки, , ТекстЯчейки);
КонецЦикла;
// 2. Заполняем таблицу значений строками с данными
Для НомерСтроки = СтрокаНачалаДанных По ВысотаТаблицы Цикл
// Проверяем, не является ли вся строка пустой (чтобы не плодить пустые строки в ТЗ)
СтрокаПустая = Истина;
Для НомерКолонки = 1 По ШиринаТаблицы Цикл
Если ЗначениеЗаполнено(ТабДок.Область(НомерСтроки, НомерКолонки).Текст) Тогда
СтрокаПустая = Ложь;
Прервать;
КонецЕсли;
КонецЦикла;
Если СтрокаПустая Тогда
Продолжить; // Пропускаем пустую строку
КонецЕсли;
НоваяСтрока = ТЗ.Добавить();
Для НомерКолонки = 1 По ШиринаТаблицы Цикл
Ячейка = ТабДок.Область(НомерСтроки, НомерКолонки);
ИмяТекущейКолонки = ТЗ.Колонки[НомерКолонки - 1].Имя;
// Если ячейка содержит типизированное значение (например, число или дату),
// считываем именно его, а не просто текст форматирования.
Если Ячейка.СодержитЗначение Тогда
НоваяСтрока[ИмяТекущейКолонки] = Ячейка.Значение;
Иначе
НоваяСтрока[ИмяТекущейКолонки] = Ячейка.Текст;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат ТЗ;
КонецФункции
Преимущества этого ручного подхода очевидны:
СодержитЗначение позволяет получать реальные числа, даты и булевы значения вместо текстовых представлений (например, вы получите число 1000 вместо текстовой строки "1 000,00 руб.").Если вы ведете разработку в современных типовых конфигурациях (таких как «Управление торговлей 11», «ERP Управление предприятием», «Комплексная автоматизация» или «Бухгалтерия предприятия 3.0»), вам нет необходимости писать велосипеды для разбора табличных документов.
В состав Библиотеки стандартных подсистем (БСП) входит мощная и удобная подсистема «Загрузка данных из файла». Она предоставляет готовый интерфейс для сопоставления колонок, автоматической валидации типов, поиска ссылочных данных (например, элементов справочников по наименованию или ИНН) и обработки ошибок. Использование стандартных механизмов БСП сокращает время разработки программного кода и обеспечивает привычный интерфейс для конечных пользователей системы.
Подводя итоги, отметим ключевые выводы нашей работы:
ОписаниеИсточникаДанных кроется в пустой первой строке передаваемой области макета.ПостроительОтчета и ОписаниеИсточникаДанных.