Получаем уровень группировки ячейки табличного документа

23.03.20

Разработка - Универсальные функции

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

Как-то мне понадобилось при обработке сформированного отчета понять к какому уровню группировки относится та или иная ячейка. Тут нужно напомнить, что группировки, во-первых, бывают многоуровневыми, а, во-вторых, горизонтальными и вертикальными (на скриншотах виден пример такого отчета). Помимо этого, группировки по колонкам одной строки, могут отличаться от группировок колонок другой строки, если строки объединены в свой формат колонок (это когда колонки в строках имеет различную ширину в разных строках).

Попытался найти что-то уже кем-то разработанное решение, но обычно все обсуждения сводились к альтернативным  способам, например, анализировать  настройки СКД или значения расшифровок  в ячейки.  Мне же не хотелось концентрироваться на том как был сформирован табличный документ, а просто взять сформированный табличный документ даже просто из файла *.mxl и определять уровень группировки любой ячейки.

Единственное, что я нашёл подходящее для моей задачи, это была вот эта статья: //infostart.ru/public/182115/. В данной разработке использовалась функция определения уровня ячейки. Но при использовании обнаружил следующее:

  •  в ней нет функции определении уровня группировок колонок, а только функция определения уровня группировки строк;
  •   при наличии в табличном документе группировок колонок функция определения уровня строк перестаёт работать.

Далее я натолкнулся на хорошую статья //infostart.ru/public/562724/, где используется сериализация и разбор структуры табличного документа. И мне захотелось просто сделать функции, куда передаёшь координаты ячейки и получаешь уровни колонок и строк группировок.

Итак, у вас есть форма обработки и на ней размещен  реквизит формы табличный документ (ТабДок), далее становимся на любую ячейку и нажимаем кнопку "ПолучитьУровниТекущейЯчейки". Выводятся сообщения с номерами уровней группировки  строк и колонок ячейки.  Если ячейка не входят в группировки, то её уровень равен. 1 (единица).

Вот код: 

&НаКлиенте
Процедура ПолучитьУровниТекущейЯчейки(Команда)
	
	//ТабДок - рекизит на форме (Табличный документ)
	//и вынесен на форму в элемент "ТабДок"
	НомерСтроки = Элементы.ТабДок.ТекущаяОбласть.Верх;
	НомерКолонки = Элементы.ТабДок.ТекущаяОбласть.Лево;
	
	УровеньЯчейки = ПолучитьУровеньГруппировкиЯчейки(ТабДок,НомерСтроки,НомерКолонки);
	
	Сообщить("Уровни группировок ячейки (" + НомерСтроки + "," + НомерКолонки + "," + НомерСтроки +"," + НомерКолонки + ")");
	Сообщить(Символы.Таб + "Уровень строки: " + УровеньЯчейки.УровеньСтроки);
	Сообщить(Символы.Таб + "Уровень колонки: " + УровеньЯчейки.УровеньКолонки);
	
	
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьУровеньГруппировкиЯчейки(ТабДок, НомерСтроки,НомерКолонки)
	
	Перем УровеньЯчейки;
	
	УровеньЯчейки = Новый Структура("УровеньСтроки,УровеньКолонки",1,1);
	
	мЗапись = Новый ЗаписьXML;
	мЗапись.УстановитьСтроку("UTF-8");
	
	СериализаторXDTO.ЗаписатьXML(мЗапись,ТабДок);
	
	ТекстXML = мЗапись.Закрыть();
	
	мЧтение = Новый ЧтениеXML;
	мЧтение.УстановитьСтроку(ТекстXML);
	ПострDOM = Новый ПостроительDOM;
	ДанныеDOM = ПострDOM.Прочитать(мЧтение);
	
	//уровни строк
	ТаблицаУровнейПоСтрокам = ПолучитьТаблицуУровнейПоСтрокам(ДанныеDOM);
	строкаУровняПоСтрокам = ТаблицаУровнейПоСтрокам.Найти(НомерСтроки,"Номер");
	Если строкаУровняПоСтрокам <> Неопределено Тогда
		УровеньЯчейки.УровеньСтроки = строкаУровняПоСтрокам.Уровень;
	КонецЕсли;	
	
    //уровни колонок
	ТаблицаУровнейПоКолонкам = ПолучитьТаблицуУровнейПоКолонкам(ДанныеDOM);
	строкиУровнейКолонокПоНомерамСтрок = ТаблицаУровнейПоКолонкам.НайтиСтроки(Новый Структура("НомерСтроки,ЕстьУровни", НомерСтроки,Истина));
	Если строкиУровнейКолонокПоНомерамСтрок.Количество() > 0 Тогда
		ТаблицаКолонок = строкиУровнейКолонокПоНомерамСтрок[0].ТаблицаКолонок;
		строкаУровняКолонки = ТаблицаКолонок.Найти(НомерКолонки, "Номер");
		Если строкаУровняКолонки <> Неопределено Тогда
			УровеньЯчейки.УровеньКолонки = строкаУровняКолонки.Уровень;
		КонецЕсли;	
	КонецЕсли;
	
	Возврат УровеньЯчейки;
	
КонецФункции	

&НаСервереБезКонтекста
Функция ПолучитьТаблицуУровнейПоСтрокам(ДанныеDOMТабДок)
	
	ТаблицаУровней = Новый ТаблицаЗначений;
	ТаблицаУровней.Колонки.Добавить("Номер",Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(10,0))); 
	ТаблицаУровней.Колонки.Добавить("Уровень",Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(10,0))); 
	
	УзлыУровнейDOMТабДок =  ДанныеDOMТабДок.ПолучитьЭлементыПоИмени("vg");
	ПредКонецГруппировки = 0;
	Для инд = 0 по УзлыУровнейDOMТабДок.Количество() - 1 Цикл
		
		УзелГруппы = УзлыУровнейDOMТабДок[инд];
		
		НачалоГруппировки = 0;
		КонецГруппировки = 0;
		
		Для н = 0 По УзелГруппы.ДочерниеУзлы.Количество() - 1 Цикл
			ПодУзел =  УзелГруппы.ДочерниеУзлы[н];
			Если ПодУзел.ИмяУзла = "b" Тогда
				НачалоГруппировки = Число(СокрЛП(ПодУзел.ТекстовоеСодержимое));
			ИначеЕсли ПодУзел.ИмяУзла = "e" Тогда
				КонецГруппировки = Число(СокрЛП(ПодУзел.ТекстовоеСодержимое));
			КонецЕсли;
		КонецЦикла;
		
		Если НачалоГруппировки = 0 Тогда Продолжить КонецЕсли;
		
		// если конца группировки не оказалось, то одна позиция в подгруппе
		Если КонецГруппировки = 0 Тогда
			КонецГруппировки = НачалоГруппировки
		КонецЕсли; 
		
		//конец область увеличить нужно на 1 
		КонецГруппировки = КонецГруппировки + 1; 
		РодительскийУровень = 0;
		Для Номер = НачалоГруппировки по КонецГруппировки Цикл
				
			строкаТаблицы = ТаблицаУровней.Найти(Номер, "Номер");
			Если строкаТаблицы = Неопределено Тогда
				строкаТаблицы = ТаблицаУровней.Добавить();
				строкаТаблицы.Номер = Номер;
				строкаТаблицы.Уровень = РодительскийУровень + 1;
				Если Номер = НачалоГруппировки Тогда
					РодительскийУровень = 1;
				КонецЕсли;	
			Иначе
				Если Номер = НачалоГруппировки Тогда
					РодительскийУровень = строкаТаблицы.Уровень
				Иначе
					строкаТаблицы.Уровень = РодительскийУровень + 1;	
				КонецЕсли;	
				
			КонецЕсли;
				
		КонецЦикла;	
		
	КонецЦикла;
	
	Возврат ТаблицаУровней;
	
КонецФункции

&НаСервереБезКонтекста
Функция ПолучитьТаблицуУровнейПоКолонкам(ДанныеDOMТабДок)
	
	ТаблицаУровней = Новый ТаблицаЗначений;
	ТаблицаУровней.Колонки.Добавить("НомерСтроки",Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(10,0))); 
	ТаблицаУровней.Колонки.Добавить("columnsID",Новый ОписаниеТипов("Строка",Новый КвалификаторыСтроки(36))); 
	ТаблицаУровней.Колонки.Добавить("ТаблицаКолонок"); 
	ТаблицаУровней.Колонки.Добавить("Уровень",Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(10,0))); 
	ТаблицаУровней.Колонки.Добавить("ЕстьУровни",Новый ОписаниеТипов("Булево")); 
	
	//Определяем сколько строк
	//и идентификатор колонок columnsID, которые будут заполнены в тех строках
	//где своё количество колонок (новый формат строк)
	УзлыУровнейDOMТабДок =  ДанныеDOMТабДок.ПолучитьЭлементыПоИмени("rowsItem");
	Для инд = 0 по УзлыУровнейDOMТабДок.Количество() - 1 Цикл
		УзелГруппы = УзлыУровнейDOMТабДок[инд];
		

		строкаТаблицыНомерСтроки = ТаблицаУровней.Добавить();
		
		ТаблицаКолонок = Новый ТаблицаЗначений;
		ТаблицаКолонок.Колонки.Добавить("Номер",Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(10,0))); 
		ТаблицаКолонок.Колонки.Добавить("Уровень",Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(10,0))); 
		
		строкаТаблицыНомерСтроки.ТаблицаКолонок = ТаблицаКолонок;
		
		Для н = 0 По УзелГруппы.ДочерниеУзлы.Количество() - 1 Цикл
			ПодУзел =  УзелГруппы.ДочерниеУзлы[н];
			Если ПодУзел.ИмяУзла = "index" Тогда
				строкаТаблицыНомерСтроки.НомерСтроки = Число(ПодУзел.ТекстовоеСодержимое) + 1;
			ИначеЕсли ПодУзел.ИмяУзла = "row" Тогда
				ПодУзел2 = ПодУзел.ДочерниеУзлы;
				Для м = 0 по ПодУзел.ДочерниеУзлы.Количество() - 1 Цикл
					ПодУзел2 = ПодУзел.ДочерниеУзлы[м];
					Если ПодУзел2.ИмяУзла = "columnsID" Тогда
						строкаТаблицыНомерСтроки.columnsID = СокрЛП(ПодУзел.ТекстовоеСодержимое);
						Прервать;
					КонецЕсли;	
				КонецЦикла;	
			КонецЕсли;
		КонецЦикла;
	КонецЦикла;	
	
	УзлыУровнейDOMТабДок =  ДанныеDOMТабДок.ПолучитьЭлементыПоИмени("hg");
	
	Для инд = 0 по УзлыУровнейDOMТабДок.Количество() - 1 Цикл
		
		УзелГруппы = УзлыУровнейDOMТабДок[инд];
		
		columnsID = Неопределено;
		НачалоГруппировки = 0;
		КонецГруппировки = 0;
		
		Для н = 0 По УзелГруппы.ДочерниеУзлы.Количество() - 1 Цикл
			ПодУзел =  УзелГруппы.ДочерниеУзлы[н];
			Если ПодУзел.ИмяУзла = "columnsID" Тогда
				columnsID = СокрЛП(ПодУзел.ТекстовоеСодержимое);
			ИначеЕсли ПодУзел.ИмяУзла = "b" Тогда
				НачалоГруппировки = Число(СокрЛП(ПодУзел.ТекстовоеСодержимое));
			ИначеЕсли ПодУзел.ИмяУзла = "e" Тогда
				КонецГруппировки = Число(СокрЛП(ПодУзел.ТекстовоеСодержимое));
			КонецЕсли;
		КонецЦикла;
		
		Если НачалоГруппировки = 0 Тогда Продолжить КонецЕсли;
		
		// если конца группировки не оказалось, то одна позиция в подгруппе
		Если КонецГруппировки = 0 Тогда
			КонецГруппировки = НачалоГруппировки
		КонецЕсли; 
		
		//конец область увеличить нужно на 1 
		КонецГруппировки = КонецГруппировки + 1;
		
		строкиКолонки = ТаблицаУровней.НайтиСтроки(Новый Структура("columnsID", columnsID));
		ПредКонецГруппировки = 0;
		Для НомерСтроки = 0 по строкиКолонки.Количество()  - 1 Цикл
			
			строкиКолонки.Получить(НомерСтроки).ЕстьУровни = Истина;
		    ТаблицаКолонок = строкиКолонки.Получить(НомерСтроки).ТаблицаКолонок;
			РодительскийУровень = 0;
			Для Номер = НачалоГруппировки по КонецГруппировки Цикл
				
				строкаТаблицы = ТаблицаКолонок.Найти(Номер, "Номер");
				Если строкаТаблицы = Неопределено Тогда
					строкаТаблицы = ТаблицаКолонок.Добавить();
					строкаТаблицы.Уровень = РодительскийУровень  + 1;
					строкаТаблицы.Номер = Номер;
					Если Номер = НачалоГруппировки Тогда
						РодительскийУровень = 1;
					КонецЕсли;	
				Иначе
					Если Номер = НачалоГруппировки Тогда
						РодительскийУровень = строкаТаблицы.Уровень
					Иначе
						строкаТаблицы.Уровень = РодительскийУровень + 1;	
					КонецЕсли;	
				КонецЕсли;
				
			КонецЦикла;	
			
		КонецЦикла;
		
	КонецЦикла;
	
	Возврат ТаблицаУровней;
	
КонецФункции

 

Проверял работает на 8.3, 8.2, должно работать и на 8.1. 

Не претендую на лаконичность кода, хотелось, чтобы бы всё было понятно.

Вступайте в нашу телеграмм-группу Инфостарт

Табличный документ ТабДок уровень группировки ячейка функции

См. также

Загрузка и выгрузка в Excel Универсальные функции Программист 1С:Предприятие 8 Россия Бесплатно (free)

Описанный ниже подход позволяет в три шага заполнять формулы в Excel файлы, вне зависимости от ОС сервера (MS Windows Server или Linux). Подход подразумевает отказ от работы с COM-объектом в пользу работы через "объектную модель документа" (DOM).

30.10.2025    3730    Abysswalker    9    

45

Универсальные функции Работа с интерфейсом Программист 1С:Предприятие 8 Бесплатно (free)

Порой необходимо временно отключить расширение 1С, не удаляя его, чтобы не потерять данные. Но в этом случае при каждом запуске всем будет лезть уведомление о неактивном расширении, хотя очевидно, это техническая информация, которой не стоит лишний раз пугать пользователей.

14.05.2025    7000    DeerCven    15    

59

Универсальные функции Программист 1С:Предприятие 8 1C:Бухгалтерия Бесплатно (free)

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

21.05.2024    50521    dimanich70    83    

170

Универсальные функции Программист 1С:Предприятие 8 1C:Бухгалтерия Абонемент ($m)

Задача: вставить картинку из буфера обмена на форму средствами платформы 1С.

1 стартмани

18.03.2024    7462    6    John_d    13    

59

Универсальные функции Программист Стажер 1С:Предприятие 8 1C:Бухгалтерия Бесплатно (free)

Пришлось помучиться с GUID-ами немного, решил поделиться опытом, мало ли кому пригодится.

12.02.2024    63762    atdonya    31    

70

Универсальные функции Программист 1С:Предприятие 8 Бесплатно (free)

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

30.11.2023    9329    ke.92@mail.ru    17    

68
Вознаграждение за ответ
Показать полностью
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. RustIG 1937 23.03.20 12:12 Сейчас в теме
(0) решение обратных задач всегда сложнее. а потому более ценно.
Спасибо за решение!
2. Diakr 13.05.20 11:19 Сейчас в теме
Спасибо за решение!
Помогло в создании собственной системы оформления отчетов.
5. maxx 1000 13.05.20 12:26 Сейчас в теме
(2) рад, что помогло
3. RustIG 1937 13.05.20 12:02 Сейчас в теме
(0) Максим, заберите пож-та себе вознаграждение :) Висит и висит тут...
4. maxx 1000 13.05.20 12:26 Сейчас в теме +1 $m
(3) а я и не знаю как забирать, все равно спасибо раз такое дело
6. RustIG 1937 13.05.20 13:25 Сейчас в теме
(4) найдите под публикацией любой свой комментарий, отметьте его Лучшим (справа внизу комментария кнопка есть), далее найдите рядышком кнопку - Передать вознаграждение.
Передайте автору лучшего комментария - то есть себе
7. Ankare 110 28.03.21 12:15 Сейчас в теме
Можно на порядки ускорить работу кода, и уменьшить объем расходуемой памяти, заменив таблицы значений на соответствия
8. maxx 1000 28.03.21 14:40 Сейчас в теме
(7) замечание конечно не по теме статьи, но всё-таки если есть статья с анализом что и как лучше использовать таблицы или соответствия, укажите ссылку
9. Ankare 110 29.03.21 22:27 Сейчас в теме
(8) к сожалению статьи писать сейчас времени нет. Если есть желание, сравните показатели со своим примером. в любом случае статья пригодилась. спасибо! мое сообщение нацелено только на то, чтоб улучшить идею

	СоответствиеУровней = Новый Соответствие;
	
	УзлыУровнейDOMТабДок =  ДанныеDOMТабДок.ПолучитьЭлементыПоИмени("vg");
	ПредКонецГруппировки = 0;
	Для инд = 0 по УзлыУровнейDOMТабДок.Количество() - 1 Цикл
		
		УзелГруппы = УзлыУровнейDOMТабДок[инд];
		
		НачалоГруппировки = 0;
		КонецГруппировки = 0;
		
		Для н = 0 По УзелГруппы.ДочерниеУзлы.Количество() - 1 Цикл
			ПодУзел =  УзелГруппы.ДочерниеУзлы[н];
			Если ПодУзел.ИмяУзла = "b" Тогда
				НачалоГруппировки = Число(СокрЛП(ПодУзел.ТекстовоеСодержимое));
			ИначеЕсли ПодУзел.ИмяУзла = "e" Тогда
				КонецГруппировки = Число(СокрЛП(ПодУзел.ТекстовоеСодержимое));
			КонецЕсли;
		КонецЦикла;
		
		Если НачалоГруппировки = 0 Тогда Продолжить КонецЕсли;
		
		// если конца группировки не оказалось, то одна позиция в подгруппе
		Если КонецГруппировки = 0 Тогда
			КонецГруппировки = НачалоГруппировки
		КонецЕсли; 
		
		//конец область увеличить нужно на 1 
		КонецГруппировки = КонецГруппировки + 1; 
		РодительскийУровень = 0;
		//ТаблицаУровней.Индексы.Очистить();
		//ТаблицаУровней.Индексы.Добавить("Номер");
		Для Номер = НачалоГруппировки по КонецГруппировки Цикл
				
			ТекУровень = СоответствиеУровней.Получить(Номер);
			Если ТекУровень = Неопределено Тогда
				//строкаТаблицы = ТаблицаУровней.Добавить();
				//строкаТаблицы.Номер = Номер;
				//строкаТаблицы.Уровень = РодительскийУровень + 1;
				СоответствиеУровней.Вставить(Номер, РодительскийУровень + 1);	
				Если Номер = НачалоГруппировки Тогда
					РодительскийУровень = 1;
				КонецЕсли;	
			Иначе
				Если Номер = НачалоГруппировки Тогда
					//РодительскийУровень = строкаТаблицы.Уровень;
					РодительскийУровень = ТекУровень;
				Иначе
					СоответствиеУровней.Вставить(Номер, РодительскийУровень + 1);	
				КонецЕсли;	
				
			КонецЕсли;
				
		КонецЦикла;	
		
	КонецЦикла;
	
	Возврат СоответствиеУровней;
Показать
10. maxx 1000 30.03.21 07:04 Сейчас в теме
(9) я уже точно не помню, но скорее всего в первых вариантах алгоритмах где-то использовался поиск в ТЗ методом НайтиСтроки по нескольким колонкам
11. Tejmur 16.06.21 15:57 Сейчас в теме
Большое спасибо!
Очень выручили!
12. hakerxp 3296 19.07.21 13:38 Сейчас в теме
Добрый день! Данный механизм подходит, когда один лист в файле эксель. Я попробовал взять 2 листа экселя - на каждом свои группировки.
И оказывается таб. док. не имеется связки через xml со своими группировками. Они просто идут перечислением вида с 3 по 5 и 10 по 15 и т.д. Но связки с листами они не имеют. И это очень большой недочет платформы 1С. Но как показывает практика - кроме как через COM адекватно получить значение уровня группировки не представляется возможным. Даже Python с множеством библиотек так и не может получить уровень группировки.
13. maxx 1000 19.07.21 13:44 Сейчас в теме
(12) речь в статье о табличном документе 1с а не листах Excel
14. hakerxp 3296 19.07.21 14:29 Сейчас в теме
Это не имеет значения. Вот представьте что у вас в табличном документе не одна область, 2 и более, и там разные группировки. Вот сопоставить вы их уже не сможете т.к. нет четкой связи между областями и группировками.
15. maxx 1000 19.07.21 14:37 Сейчас в теме
да, думаю связи областей и группировок нет, они не зависимо себя ведут, в одной группировки (в строках группировках) может быть много областей
16. seperblunt2 01.08.21 11:47 Сейчас в теме
Спасибо! очень помогло
17. AnPet 3 21.08.23 13:54 Сейчас в теме
Большое спасибо за прозрачное и лаконичное пояснение и за внятный код.
Воспользовался частью решения для строк.
Получаю с сайта поставщика файл excel с группировками каталога товаров и читаю его в ТабличныйДокумент, чтобы избежать использование внешнего Excel.
Алгоритм формирования дерева у меня свой, поэтому я просто возвращаю таблицу уровней и в своем алгоритме получаю из нее для текущей сроки ее уровень. Правда, в конечном итоге применил алгоритм с Соответствием из поста 9. Субъективно он выполняется чуть быстрей, и затем получить по ключу соответствия уровень тоже как-то более логично выглядит.
Для отправки сообщения требуется регистрация/авторизация