gifts2017

Дерево значений, которое негде посмотреть

Опубликовал Яков Коган (Yashazz) в раздел Программирование - Практика программирования

Метод "ВыбратьСтроку" остался в золотом веке толстого клиента, но необходимость окинуть одним взглядом всё дерево порой возникает. Предлагаю простое решение в виде одной функции - вывода в текстовое представление.

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

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

Никаких особых хитростей нет, кроме разве что одного места - определения максимального количества уровней в дереве. Признаю, что сделал это не самым оптимальным образом, через сериализацию (да ещё и всего дерева, когда хватило бы одной колонки) - можно и иначе, желающие да обрящут. Вот http://infostart.ru/profile/28527/ наверняка придумал бы иной способ, верю.

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

Используется вывод в текстовый документ с помощью текстового же макета:

// Выводит данные ДереваЗначений в ТекстовыйДокумент, пригодный к рассмотрению в отладчике, окне сообщений и показу.
//
// Параметры:
//    рВетка - дерево значений, подлежащее выводу. Может иметь почти любую глубину иерархии, количество и тип колонок. Если хватит
//        объявленных позиционных строк-заполнителей, всё поместится. Если иерархия слишком глубока, строки просто надо нарастить;
//    рТекст - на входе должен быть равен Неопределено; на выходе по окончании работы содержит результатный текстовый документ;
//    рПараметры - структура дополнительных настроек, допустимые ключи:
//        Колонки - структура колонок, которые подлежат выводу. Если пуста или имеет тип, отличный от структуры, выводятся все колонки;
//           если в ключах структуры указаны имена колонок, будут выведены только они. В значениях структуры можно передать строковое
//           представление формата, согласно которому должны будут форматироваться выводимые значения. По умолчанию формата нет;
//        ПоказыватьУровни - булево, управляет выводом №№ уровней (метод Уровень()), по умолчанию выключено;
//        ШагОтступа - число, определяющее шаг в символах, используемый для показа псевдографики иерархии веток в дереве.
//
&НаСервереБезКонтекста
Процедура ВывестиДеревоЗначенийВТекст(Знач рВетка,рТекст,Знач рПараметры=Неопределено)
Попытка
    Если
рТекст=Неопределено Тогда // первая итерация, шапочный вызов исходной части
        //---------------------------------------------------------------------------------------------------------------------------
        // Разбираем входные данные
       
Если ТипЗнч(рВетка)<>Тип("ДеревоЗначений") Тогда Возврат КонецЕсли;
        Если
ТипЗнч(рПараметры)<>Тип("Структура") Тогда рПараметры=Новый Структура КонецЕсли;
       
стрКолонок=?(рПараметры.Свойство("Колонки"),рПараметры.Колонки,Неопределено);
        Если
ТипЗнч(стрКолонок)<>Тип("Структура") Тогда стрКолонок=Новый Структура КонецЕсли;
       
рПоказыватьУровни=?(рПараметры.Свойство("ПоказыватьУровни"),рПараметры.ПоказыватьУровни,Ложь); // затратное дело, кстати
       
рШагОтступа=?(рПараметры.Свойство("ШагОтступа"),рПараметры.ШагОтступа,2); // для отображения отступа в колонке иерархии

        //---------------------------------------------------------------------------------------------------------------------------
        // Определяем максимальное количество уровней
        // Также можно получить максимальный уровень, "плоско" перебрав все строки (например, получив их через НайтиСтроки)
        // и в цикле для каждой вызывая Уровень() и определяя максимум.
        // Вариант через СКД и служебное поле "Уровень" ни на одной известной мне платформе не работоспособен (нехватка памяти).
        //
        //
        //рДеревоДляТеста=КакНибудьСкопироватьДерево(рВетка);
        //рДеревоДляТеста.Колонки.Вставить(0,"_Level",Новый ОписаниеТипов("Булево")); // самый экономный вариант, с сохранением иерархии
        //мНенужных=Новый Массив;
        //Для й=1 По рДеревоДляТеста.Колонки.Количество()-1 Цикл
        //  мНенужных.Добавить(рДеревоДляТеста.Колонки[й]);
        //КонецЦикла;
        //Для каждого кол Из мНенужных Цикл
        //  рДеревоДляТеста.Колонки.Удалить(кол);
        //КонецЦикла;
        //
       
рДеревоДляТеста=рВетка; // пока сериализуем прямо всё дерево (неоптимально, но...)
       
рЗапись=Новый ЗаписьXML;
       
рЗапись.УстановитьСтроку();
       
СериализаторXDTO.ЗаписатьXML(рЗапись,рДеревоДляТеста);
       
стро=рЗапись.Закрыть();
       
//
       
стро=СтрЗаменить(стро,"xmlns=","xmlns:myns1C="); // иначе не будет работать XPath
        //
       
рЧтение=Новый ЧтениеXML;
       
рЧтение.УстановитьСтроку(стро);
       
постр=Новый ПостроительDOM;
       
рДокументДОМ=постр.Прочитать(рЧтение);

       
рВыражение="/ValueTree/row"; максКолвоУровней=1;
       
рРазыменователь=Новый РазыменовательПространствИменDOM(рДокументДОМ);
        Пока Истина Цикл
           
#Если Клиент Тогда
               
ОбработкаПрерыванияПользователя();
           
#КонецЕсли
           
рРезультат=рДокументДОМ.ВычислитьВыражениеXPath(рВыражение,рДокументДОМ,рРазыменователь,ТипРезультатаDOMXPath.Любой);
            Если
рРезультат.ПолучитьСледующий()=Неопределено Тогда Прервать КонецЕсли;
           
рВыражение=рВыражение+"/row";
           
максКолвоУровней=максКолвоУровней+1;
        КонецЦикла;
       
максКолвоУровней=максКолвоУровней-1; // можно и так: СтрЧислоВхождений(рВыражение,"/row")-1;

        //---------------------------------------------------------------------------------------------------------------------------
        // готовим исходный макет вывода
       
строПробелы="                                                                                                                                                                                                                 ";
       
строРазделители="===============================================================================================";
       
строОтступы="___________________________________________________________________________________________"; // тут нужны чуть для другого
        //
       
секШапка1="|[_HCS"+Лев(строПробелы,максКолвоУровней*рШагОтступа)+"]|"; // HierarchyColumnShow
       
секГорРазделитель1="|"+Лев(строОтступы,максКолвоУровней*рШагОтступа+6)+"|"; // здесь именно Отступы!
       
рШиринаКолонкиИерархии=СтрДлина(секШапка1)-2;
       
строПолейШапки="#Поле _HCS
        |   #Выравнивание Центр"
;
       
строПолейЗаписи="";
       
//
       
Если рПоказыватьУровни Тогда
           
секШапка1=секШапка1+"[_Level]|";
           
секГорРазделитель1=секГорРазделитель1+Лев(строРазделители,8)+"|";
           
строПолейШапки=строПолейШапки+"
            |#Поле _Level
            |   #Выравнивание Центр"
;
           
строПолейЗаписи="#Поле _Level
            |   #Выравнивание Центр
            |   #Забивать Истина"
;
        КонецЕсли;
       
//
       
мИменКолонок=Новый Массив;
        Для каждого
кол Из рВетка.Колонки Цикл
            Если
стрКолонок.Количество()<>0 и не стрКолонок.Свойство(кол.Имя) Тогда Продолжить КонецЕсли; // чётко указаны конкретные колонки
           
рДлинаИмениКолонки=СтрДлина(кол.Имя);
           
рНужнаяШирина=Макс(?(кол.Ширина<3,10,кол.Ширина),рДлинаИмениКолонки);
           
// к сожалению, ключевое слово "#Поля" неприменимо - платформа падает - поэтому делаем всё сами
           
секШапка1=секШапка1+"["+кол.Имя+Лев(строПробелы,рНужнаяШирина-рДлинаИмениКолонки)+"]|";
           
секГорРазделитель1=секГорРазделитель1+Лев(строРазделители,рНужнаяШирина+1)+"=|";
           
строПолейШапки=строПолейШапки+"
            |#Поле "
+кол.Имя+"
            |   #Выравнивание Центр"
;
           
рОписТипов=кол.ТипЗначения;
            Если
рОписТипов.Типы().Количество()=1 и рОписТипов.СодержитТип(Тип("Булево")) Тогда
               
рВыравнивание="Центр";
            ИначеЕсли
рОписТипов.СодержитТип(Тип("Число")) Тогда
               
рВыравнивание="Право";
            ИначеЕсли
рОписТипов.СодержитТип(Тип("Строка")) и рОписТипов.КвалификаторыСтроки.Длина=0 Тогда
               
рВыравнивание="ПоШирине";
            Иначе
               
рВыравнивание="Лево";
            КонецЕсли;
           
строПолейЗаписи=строПолейЗаписи+?(ПустаяСтрока(строПолейЗаписи),"",Символы.ПС)+"#Поле "+кол.Имя+"
            |   #Выравнивание "
+рВыравнивание;
            Попытка
                Если
ЗначениеЗаполнено(стрКолонок[кол.Имя]) Тогда // указан формат, уточняющий показ колонки
                   
строПолейЗаписи=строПолейЗаписи+"
                    |   #Формат """
+СокрЛП(стрКолонок[кол.Имя])+"""";
                КонецЕсли;
            Исключение
            КонецПопытки;
           
мИменКолонок.Добавить(кол.Имя);
        КонецЦикла;
       
секШапка2=СтрЗаменить(СтрЗаменить(секШапка1,"[","<"),"]",">");
       
секЗапись1=секШапка1; секЗапись2=секШапка2; // пусть они по дизайну пока не отличаются
       
секГорРазделитель2=СтрЗаменить(СтрЗаменить(секГорРазделитель1,"=","-"),"|","+");
       
// обработаем нормальное обрамление шапки колонки иерархии
       
секГорРазделитель1=СтрЗаменить(секГорРазделитель1,"_","=");
       
секГорРазделитель2=СтрЗаменить(секГорРазделитель2,"_"," ");
       
//
       
тМакет=Новый ТекстовыйДокумент;
       
тМакет.ДобавитьСтроку("#Область Шапка");
       
тМакет.ДобавитьСтроку(строПолейШапки);
       
тМакет.ДобавитьСтроку(секГорРазделитель1);
       
тМакет.ДобавитьСтроку(секШапка1);
       
тМакет.ДобавитьСтроку(секШапка2);
       
тМакет.ДобавитьСтроку(секГорРазделитель1);
       
тМакет.ДобавитьСтроку("#КонецОбласти");
       
тМакет.ДобавитьСтроку("");
       
тМакет.ДобавитьСтроку("#Область Запись");
       
тМакет.ДобавитьСтроку(строПолейЗаписи);
       
тМакет.ДобавитьСтроку(секЗапись1);
       
тМакет.ДобавитьСтроку(секЗапись2);
       
тМакет.ДобавитьСтроку(секГорРазделитель2);
       
тМакет.ДобавитьСтроку("#КонецОбласти");
       
рПараметры.Вставить("ИсходныйМакет",тМакет); // пусть будет

        //---------------------------------------------------------------------------------------------------------------------------
        // Запускаем вывод в итоговый документ
       
рТекст=Новый ТекстовыйДокумент;
       
сек=тМакет.ПолучитьОбласть("Шапка");
       
сек.Параметры._HCS="Иерархия";
        Для каждого
имякол Из мИменКолонок Цикл
           
кол=рВетка.Колонки[имякол];
           
сек.Параметры[кол.Имя]=?(ПустаяСтрока(кол.Заголовок),кол.Имя,кол.Заголовок);
        КонецЦикла;
       
рТекст.Вывести(сек);
       
//
       
пар=Новый Структура;
       
пар.Вставить("МассивИмёнКолонок",мИменКолонок);
       
пар.Вставить("ТекущаяСекция",тМакет.ПолучитьОбласть("Запись"));
       
пар.Вставить("ШиринаКолонкиИерархии",рШиринаКолонкиИерархии);
       
пар.Вставить("ПоказыватьУровни",рПоказыватьУровни);
       
пар.Вставить("Отступ",0);
       
пар.Вставить("ШагОтступа",рШагОтступа);
       
ВывестиДеревоЗначенийВТекст(рВетка,рТекст,пар);

    Иначе
       
// очередная итерация, вывод в текстовый документ
       
мИменКолонок=рПараметры.МассивИмёнКолонок;
       
рСекция=рПараметры.ТекущаяСекция;
       
рОтступ=рПараметры.Отступ;
       
рШиринаКолонкиИерархии=рПараметры.ШиринаКолонкиИерархии;
       
рПоказыватьУровни=рПараметры.ПоказыватьУровни;
       
//
       
строОтступы="_________________________________________________________________________________________________________";
       
строПробелы="                                                                                                                                                                                                                 ";
       
//
       
пар=Новый Структура;
       
пар.Вставить("МассивИмёнКолонок",мИменКолонок);
       
пар.Вставить("ТекущаяСекция",рПараметры.ТекущаяСекция);
       
пар.Вставить("ШиринаКолонкиИерархии",рШиринаКолонкиИерархии);
       
пар.Вставить("ПоказыватьУровни",рПоказыватьУровни);
       
пар.Вставить("ШагОтступа",рПараметры.ШагОтступа);
       
пар.Вставить("Отступ",рОтступ+рПараметры.ШагОтступа);
       
//
       
Для каждого рПодветка Из рВетка.Строки Цикл
           
рСекция.Параметры._HCS=Лев(строПробелы,рОтступ)+"\"+Лев(строОтступы,рШиринаКолонкиИерархии-рОтступ-1);
            Если
рПоказыватьУровни Тогда
               
рСекция.Параметры._Level=рПодветка.Уровень();
            КонецЕсли;
            Для каждого
имякол Из мИменКолонок Цикл
               
рСекция.Параметры[имякол]=рПодветка[имякол];
            КонецЦикла;
           
рТекст.Вывести(рСекция);
           
ВывестиДеревоЗначенийВТекст(рПодветка,рТекст,пар)
        КонецЦикла;
       
//
   
КонецЕсли;
Исключение
   
Сообщить("ВывестиДеревоЗначенийВТекст, ошибка: "+ОписаниеОшибки(),СтатусСообщения.ОченьВажное);
КонецПопытки;
КонецПроцедуры

Надеюсь, пригодится и упростит жизнь) 

См. также

Подписаться Добавить вознаграждение
Комментарии
1. DAnry (DAnry) 08.10.14 14:46
Интересный вариант. Действительно при отладке бывают случаи, когда надо просмотреть дерево значений. Возьму на вооружение. Спасибо! Код конечно длинный, но его же не надо каждый раз набирать ;)
2. Владимир Клименко (KliMich) 20.12.14 22:07
Спасибо! Пригодилось при разборе РегламентногоОтчета "Статистика НаукаЗП" (Главбух гаявил, что мол не правильные данные....) А там результат километнорого запроса возвращается в виде ДереваЗначений....
Очень пригодилось для разбора!