Загрузка данных из внешних источников — одна из самых частых задач разработчика 1С. Однако не всегда данные приходят в удобном формате XML или JSON. Часто мы сталкиваемся с текстовыми файлами или Excel-таблицами, где иерархия (вложенность групп и элементов) обозначена визуально — с помощью отступов или пробелов перед наименованием. Основная сложность здесь заключается в том, чтобы не «потерять» текущего родителя при переходе от глубоко вложенного элемента обратно к группе верхнего уровня.
В этой статье мы подробно разберем, как построить надежный алгоритм парсинга таких структур, проанализируем типичные ошибки и рассмотрим программные приемы, которые сделают вашу загрузку быстрой и безошибочной.
Прежде чем писать код, давайте проанализируем входящие данные. В типичном прайс-листе иерархия строится по принципу: чем больше пробелов в начале строки, тем глубже уровень вложенности. Рассмотрим пример:
Проблема возникает, когда после «Товара 1» идет строка с 4 пробелами. Нам нужно мгновенно понять, что родителем для новой строки станет группа «Клапаны», а не «CIMBERIO». Для решения этой задачи мы будем использовать концепцию стека родителей.
Первым делом нам необходимо научиться корректно определять количество отступов. Важно помнить, что в файлах могут встречаться не только обычные пробелы, но и символы табуляции или неразрывные пробелы. Разберем, как нормализовать строку перед обработкой.
Для надежного подсчета лидирующих символов мы можем использовать цикл или регулярные выражения. Рассмотрим пример функции, которая возвращает количество пробелов и очищенную строку:
Функция ПолучитьИнформациюОбОтступе(Знач ИсходнаяСтрока)
// Заменяем неразрывные пробелы и табуляцию на обычные пробелы
Стр = СтрЗаменить(ИсходнаяСтрока, Символы.НПП, " ");
Стр = СтрЗаменить(Стр, Символы.Таб, " "); // Допустим, 1 таб = 4 пробела
Результат = Новый Структура("Количество, Текст");
// Считаем количество пробелов в начале
ДлинаИсходная = СтрДлина(Стр);
СтрБезПробелов = СокрЛП(Стр);
// Находим позицию первого значащего символа
КоличествоПробелов = Найти(Стр, СтрБезПробелов) - 1;
Результат.Количество = КоличествоПробелов;
Результат.Текст = СтрБезПробелов;
Возврат Результат;
КонецФункции
Важный нюанс: как было замечено в обсуждении, иногда количество пробелов может «гулять» (например, 8 вместо 9). Поэтому в некоторых случаях эффективнее делать предварительный проход по файлу, собирать все уникальные значения отступов, сортировать их и присваивать им порядковые номера уровней (0, 1, 2...).
Теперь рассмотрим основной алгоритм. Мы будем использовать Массив (или Соответствие), где индекс будет соответствовать уровню вложенности, а значение — ссылке на созданную в базе данных группу. Это позволит нам в любой момент времени знать, кто является родителем для текущего уровня.
Разберем логику по шагам:
СтекРодителей.СтекРодителей, который находится на предыдущем уровне.СтекРодителей на текущий уровень.СтекРодителей с предыдущего уровня.Рассмотрим программную реализацию этой логики:
Процедура ЗагрузитьПрайс(ТекстовыйДокумент)
// Соответствие для хранения родителей: Ключ - КоличествоПробелов, Значение - Ссылка
СтекРодителей = Новый Соответствие;
СтекРодителей.Вставить(-1, Справочники.Номенклатура.ПустаяСсылка()); // Корневой родитель
ПоследнийУровень = -1;
Для НомерСтроки = 1 По ТекстовыйДокумент.КоличествоСтрок() Цикл
ТекСтрока = ТекстовыйДокумент.ПолучитьСтроку(НомерСтроки);
Инфо = ПолучитьИнформациюОбОтступе(ТекСтрока);
Если ПустаяСтрока(Инфо.Текст) Тогда
Продолжить;
КонецЕсли;
// Определяем родителя. Нам нужен ближайший сохраненный уровень, который меньше текущего
ТекущийРодитель = Справочники.Номенклатура.ПустаяСсылка();
МаксимальныйМеньшийУровень = -1;
Для Каждого ЗаписьИзСтека Из СтекРодителей Цикл
Если ЗаписьИзСтека.Ключ < Инфо.Количество И ЗаписьИзСтека.Ключ > МаксимальныйМеньшийУровень Тогда
МаксимальныйМеньшийУровень = ЗаписьИзСтека.Ключ;
ТекущийРодитель = ЗаписьИзСтека.Значение;
КонецЕсли;
КонецЦикла;
Если ЭтоГруппа(Инфо.Текст) Тогда // Ваша функция определения группы
НоваяГруппа = Справочники.Номенклатура.СоздатьГруппу();
НоваяГруппа.Наименование = Инфо.Текст;
НоваяГруппа.Родитель = ТекущийРодитель;
НоваяГруппа.Записать();
// Запоминаем эту группу как потенциального родителя для следующих строк
СтекРодителей.Вставить(Инфо.Количество, НоваяГруппа.Ссылка);
Иначе // Это элемент
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.Наименование = Инфо.Текст;
НовыйЭлемент.Родитель = ТекущийРодитель;
// Заполнение других реквизитов (цена, артикул)
НовыйЭлемент.Записать();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Проанализируем ситуацию, когда данных очень много. Записывать каждую строку в базу данных «на лету» может быть медленно. Кроме того, пользователю полезно сначала увидеть структуру загрузки. Рассмотрим вариант с использованием объекта ДеревоЗначений.
Мы можем сначала построить дерево в оперативной памяти, используя ту же логику со стеком. В ДеревоЗначений у каждого узла есть коллекция Строки, что позволяет легко добавлять подчиненные элементы. После того как дерево построено, мы можем рекурсивно обойти его и создать элементы в справочнике 1С внутри одной транзакции.
Преимущества такого подхода:
Для того чтобы алгоритм работал максимально эффективно, применим несколько профессиональных приемов:
НачатьТранзакцию() ... ЗафиксироватьТранзакцию(). Это ускорит загрузку в десятки раз, так как индексы базы данных будут обновляться один раз в конце, а не после каждой записи.Запрос или предварительно выгрузить структуру справочника в Соответствие.РегулярноеВыражение для парсинга отступов. На более старых версиях можно использовать COM-объект VBScript.RegExp. Это гораздо производительнее, чем ручной перебор символов в цикле.Выясним причину, по которой алгоритм может работать некорректно. Чаще всего это происходит из-за смешанных отступов (где-то пробелы, где-то табуляция). Всегда начинайте работу с функции очистки и нормализации строки. Только приведя все отступы к единому «знаменателю», можно гарантировать, что иерархия в 1С будет в точности соответствовать визуальной структуре прайс-листа.
Рассмотренный нами метод со стеком уровней является наиболее универсальным. Он одинаково хорошо работает как для простых структур, так и для глубоко вложенных каталогов с десятками уровней вложенности. Реализовав эту логику один раз, вы сможете использовать её в любых обработках загрузки данных из неструктурированных текстовых источников.