В практике разработчика 1С часто встречается задача, когда данные поступают в «плоском» виде — например, при импорте из текстовых файлов или простых Excel-таблиц. Одной из самых трудоемких ситуаций является случай, когда и названия групп, и названия товаров находятся в одной колонке, а принадлежность товара к группе определяется лишь тем, что он расположен в списке ниже заголовка этой группы. В этой статье мы подробно разберем, как программно обработать такую таблицу и превратить её в структурированное дерево значений или таблицу с иерархией.
Представим, что у нас есть объект ТаблицаЗначений с именем Табл и колонкой Кол1. Содержимое выглядит следующим образом:
Наша цель — распознать строки, содержащие признак группы, и сделать все последующие строки подчиненными этой группе до тех пор, пока не встретится следующий заголовок.
Самый прямой и эффективный способ — один раз пройти по таблице циклом и динамически создавать узлы в объекте ДеревоЗначений. Это решение оптимально, так как выполняется за один проход (сложность O(n)).
Проанализируем алгоритм: мы создаем пустое дерево, перебираем строки исходной таблицы и, если находим ключевое слово «Группа», создаем новую ветку. Если ключевого слова нет — добавляем строку в текущую активную ветку.
// Создаем структуру дерева
Дерево = Новый ДеревоЗначений;
Дерево.Колонки.Добавить("Наименование");
Переменная ТекущаяГруппа = Неопределено;
Для Каждого СтрокаТЗ Из Табл Цикл
// Проверяем, является ли строка заголовком группы
Если СтрНайти(СтрокаТЗ.Кол1, "Группа товара:") > 0 Тогда
// Извлекаем чистое название группы (удаляем префикс)
НазваниеГруппы = СокрЛП(СтрЗаменить(СтрокаТЗ.Кол1, "Группа товара:", ""));
// Добавляем корень в дерево
ТекущаяГруппа = Дерево.Строки.Добавить();
ТекущаяГруппа.Наименование = НазваниеГруппы;
Иначе
// Если это обычный товар, добавляем его в последнюю созданную группу
Если ТекущаяГруппа = Неопределено Тогда
// Обработка случая, если товары идут до первой группы
НоваяСтрока = Дерево.Строки.Добавить();
НоваяСтрока.Наименование = СтрокаТЗ.Кол1;
Иначе
НоваяСтрока = ТекущаяГруппа.Строки.Добавить();
НоваяСтрока.Наименование = СтрокаТЗ.Кол1;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Иногда создание ДеревоЗначений сразу неудобно, например, если данные нужно в дальнейшем выводить в отчет через СКД или использовать в запросах. В этом случае мы можем модифицировать исходную ТаблицаЗначений, добавив в неё колонку, которая будет указывать на владельца строки.
Рассмотрим этот метод по шагам:
Родитель.
Табл.Колонки.Добавить("Родитель", Новый ОписаниеТипов("Строка"));
ТекущееИмяГруппы = "";
// Шаг 1: Заполнение признака родителя
Для Каждого СтрокаТЗ Из Табл Цикл
Если СтрНайти(СтрокаТЗ.Кол1, "Группа товара:") > 0 Тогда
ТекущееИмяГруппы = СокрЛП(СтрЗаменить(СтрокаТЗ.Кол1, "Группа товара:", ""));
КонецЕсли;
СтрокаТЗ.Родитель = ТекущееИмяГруппы;
КонецЦикла;
// Шаг 2: Удаление служебных строк-заголовков
ПараметрыПоиска = Новый Структура("Кол1", "");
// Здесь можно использовать массив для удаления строк, содержащих "Группа товара:"
СтрокиДляУдаления = Новый Массив;
Для Каждого СтрокаТЗ Из Табл Цикл
Если СтрНайти(СтрокаТЗ.Кол1, "Группа товара:") > 0 Тогда
СтрокиДляУдаления.Добавить(СтрокаТЗ);
КонецЕсли;
КонецЦикла;
Для Каждого Стр Из СтрокиДляУдаления Цикл
Табл.Удалить(Стр);
КонецЦикла;
Проанализируем ситуацию: если слово «Группа» может встретиться в середине названия товара (например, «Подушка для группы детского сада»), метод СтрНайти может сработать ложно. Для повышения надежности в современной платформе 8.3 рекомендуется использовать метод СтрНачинаетсяС.
Посмотрим на пример проверки:
Если СтрНачинаетсяС(СокрЛП(СтрокаТЗ.Кол1), "Группа товара:") Тогда
// Это гарантированно начало строки, а не случайное совпадение в середине
КонецЕсли;
Если признак группы более сложный (например, «Группа №1:», «Группа №2:»), лучше использовать регулярные выражения. Это позволит гибко настраивать шаблоны поиска без жесткой привязки к конкретному тексту.
Разберем пример реализации через COM-объект:
RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.IgnoreCase = Истина;
RegExp.Global = Ложь;
RegExp.Pattern = "^Группа товара:\s*(.*)"; // Ищем в начале строки
Для Каждого СтрокаТЗ Из Табл Цикл
Matches = RegExp.Execute(СтрокаТЗ.Кол1);
Если Matches.Count > 0 Тогда
// Это группа. Извлекаем захваченную подстроку с названием
ИмяГруппы = Matches.Item(0).SubMatches.Item(0);
// Далее логика добавления в дерево...
КонецЕсли;
КонецЦикла;
При обработке очень больших объемов данных (десятки тысяч строк) важно учитывать следующие моменты:
Соответствие, где ключом будет название группы, а значением — ссылка на строку дерева значений.ТекстовыйДокумент.Прочитать() или ЧтениеТекста. Это существенно сэкономит оперативную память.Таким образом, задача группировки значений из одной колонки сводится к последовательному анализу строк и управлению состоянием «текущего родителя». Выбор конкретного метода (дерево, вспомогательная колонка или регулярные выражения) зависит от сложности структуры ваших данных и того, как вы планируете использовать их в дальнейшем.