Как разработать алгоритм загрузки иерархического прайс-листа с отступами в 1С?

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

Загрузка данных из внешних источников — одна из самых частых задач разработчика 1С. Однако не всегда данные приходят в удобном формате XML или JSON. Часто мы сталкиваемся с текстовыми файлами или Excel-таблицами, где иерархия (вложенность групп и элементов) обозначена визуально — с помощью отступов или пробелов перед наименованием. Основная сложность здесь заключается в том, чтобы не «потерять» текущего родителя при переходе от глубоко вложенного элемента обратно к группе верхнего уровня.

В этой статье мы подробно разберем, как построить надежный алгоритм парсинга таких структур, проанализируем типичные ошибки и рассмотрим программные приемы, которые сделают вашу загрузку быстрой и безошибочной.

Анализ структуры данных

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

  1. Клапаны (0 пробелов) — Уровень 0
  2. ....Клапаны балансировочные (4 пробела) — Уровень 1
  3. ........CIMBERIO (8 пробелов) — Уровень 2
  4. ............Товар 1 (12 пробелов) — Уровень 3

Проблема возникает, когда после «Товара 1» идет строка с 4 пробелами. Нам нужно мгновенно понять, что родителем для новой строки станет группа «Клапаны», а не «CIMBERIO». Для решения этой задачи мы будем использовать концепцию стека родителей.

Подготовка данных: нормализация и подсчет отступов

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

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


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

Важный нюанс: как было замечено в обсуждении, иногда количество пробелов может «гулять» (например, 8 вместо 9). Поэтому в некоторых случаях эффективнее делать предварительный проход по файлу, собирать все уникальные значения отступов, сортировать их и присваивать им порядковые номера уровней (0, 1, 2...).

Алгоритм загрузки с использованием массива родителей

Теперь рассмотрим основной алгоритм. Мы будем использовать Массив (или Соответствие), где индекс будет соответствовать уровню вложенности, а значение — ссылке на созданную в базе данных группу. Это позволит нам в любой момент времени знать, кто является родителем для текущего уровня.

Разберем логику по шагам:

  1. Создаем массив СтекРодителей.
  2. Читаем файл построчно.
  3. Для каждой строки определяем уровень (через количество пробелов).
  4. Если текущая строка — это группа:
    • Создаем группу в справочнике.
    • Родителем назначаем элемент из СтекРодителей, который находится на предыдущем уровне.
    • Записываем ссылку на новую группу в СтекРодителей на текущий уровень.
  5. Если текущая строка — это товар:
    • Создаем элемент.
    • Родителем назначаем группу из СтекРодителей с предыдущего уровня.

Рассмотрим программную реализацию этой логики:


Процедура ЗагрузитьПрайс(ТекстовыйДокумент)
    
    // Соответствие для хранения родителей: Ключ - КоличествоПробелов, Значение - Ссылка
    СтекРодителей = Новый Соответствие;
    СтекРодителей.Вставить(-1, Справочники.Номенклатура.ПустаяСсылка()); // Корневой родитель
    
    ПоследнийУровень = -1;
    
    Для НомерСтроки = 1 По ТекстовыйДокумент.КоличествоСтрок() Цикл
        
        ТекСтрока = ТекстовыйДокумент.ПолучитьСтроку(НомерСтроки);
        Инфо = ПолучитьИнформациюОбОтступе(ТекСтрока);
        
        Если ПустаяСтрока(Инфо.Текст) Тогда
            Продолжить;
        КонецЕсли;
        
        // Определяем родителя. Нам нужен ближайший сохраненный уровень, который меньше текущего
        ТекущийРодитель = Справочники.Номенклатура.ПустаяСсылка();
        МаксимальныйМеньшийУровень = -1;
        
        Для Каждого ЗаписьИзСтека Из СтекРодителей Цикл
            Если ЗаписьИзСтека.Ключ < Инфо.Количество И ЗаписьИзСтека.Ключ > МаксимальныйМеньшийУровень Тогда
                МаксимальныйМеньшийУровень = ЗаписьИзСтека.Ключ;
                ТекущийРодитель = ЗаписьИзСтека.Значение;
            КонецЕсли;
        КонецЦикла;
        
        Если ЭтоГруппа(Инфо.Текст) Тогда // Ваша функция определения группы
            
            НоваяГруппа = Справочники.Номенклатура.СоздатьГруппу();
            НоваяГруппа.Наименование = Инфо.Текст;
            НоваяГруппа.Родитель = ТекущийРодитель;
            НоваяГруппа.Записать();
            
            // Запоминаем эту группу как потенциального родителя для следующих строк
            СтекРодителей.Вставить(Инфо.Количество, НоваяГруппа.Ссылка);
            
        Иначе // Это элемент
            
            НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
            НовыйЭлемент.Наименование = Инфо.Текст;
            НовыйЭлемент.Родитель = ТекущийРодитель;
            // Заполнение других реквизитов (цена, артикул)
            НовыйЭлемент.Записать();
            
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Использование Дерева Значений для визуализации

Проанализируем ситуацию, когда данных очень много. Записывать каждую строку в базу данных «на лету» может быть медленно. Кроме того, пользователю полезно сначала увидеть структуру загрузки. Рассмотрим вариант с использованием объекта ДеревоЗначений.

Мы можем сначала построить дерево в оперативной памяти, используя ту же логику со стеком. В ДеревоЗначений у каждого узла есть коллекция Строки, что позволяет легко добавлять подчиненные элементы. После того как дерево построено, мы можем рекурсивно обойти его и создать элементы в справочнике 1С внутри одной транзакции.

Преимущества такого подхода:

Оптимизация производительности

Для того чтобы алгоритм работал максимально эффективно, применим несколько профессиональных приемов:

  1. Использование транзакций: Оберните цикл записи в блок НачатьТранзакцию() ... ЗафиксироватьТранзакцию(). Это ускорит загрузку в десятки раз, так как индексы базы данных будут обновляться один раз в конце, а не после каждой записи.
  2. Кэширование: Если прайс обновляется часто, перед созданием новой группы проверьте, не существует ли она уже в этом родителе. Для этого можно использовать Запрос или предварительно выгрузить структуру справочника в Соответствие.
  3. Регулярные выражения: Если вы работаете на платформе 8.3.23+, используйте встроенный объект РегулярноеВыражение для парсинга отступов. На более старых версиях можно использовать COM-объект VBScript.RegExp. Это гораздо производительнее, чем ручной перебор символов в цикле.

Выясним причину, по которой алгоритм может работать некорректно. Чаще всего это происходит из-за смешанных отступов (где-то пробелы, где-то табуляция). Всегда начинайте работу с функции очистки и нормализации строки. Только приведя все отступы к единому «знаменателю», можно гарантировать, что иерархия в 1С будет в точности соответствовать визуальной структуре прайс-листа.

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

← На главную