21 июн. 2014 г.

Генерация XGML-диаграммы движений документов для yEd

Описанное ранее создание UML-диаграммы движений имеет существенный недостаток - результат на выходе статичен, а хотелось бы иметь возможность в последствии еще внести изменения в схему. Поэтому решил написать генерацию диаграммы для какого-нибудь редактора. Искать долго не пришлось, выбор пал на yEd Graph Editor, поскольку уже продолжительное время использую ее для создания разного рода диаграмм и полностью ею доволен. Программа позволяет читать данные из .xgml файлов, которые представляют собой текстовый XML. Разобравшись по быстрому со структурой файла, набросал для себя простенькую обработку.



&НаСервереБезКонтекста
Функция ПолучитьТаблицуРегистров()

 Таблица = Новый ТаблицаЗначений;
 Таблица.Колонки.Добавить("ВидДокумента");
 Таблица.Колонки.Добавить("Регистр");
 Таблица.Колонки.Добавить("ВидРегистра");
 
 Для Каждого мДокумент Из Метаданные.Документы Цикл
  
  ВидДокумента = мДокумент.Имя;
  
  Для Каждого мДвижение Из мДокумент.Движения Цикл
   
   Строка = Таблица.Добавить();
   Строка.ВидДокумента = ВидДокумента;
   Строка.Регистр = мДвижение.Имя;
   Строка.ВидРегистра = ОпределитьВидРегистра(мДвижение);
   
  КонецЦикла;
  
 КонецЦикла;
 
 Возврат Таблица;

КонецФункции // ПолучитьТаблицуРегистров()

&НаСервереБезКонтекста
Функция ОпределитьВидРегистра(мДвижение)
 
 Перем ЗначениеВозврата;
 
 Если Метаданные.РегистрыБухгалтерии.Содержит(мДвижение) Тогда
  ЗначениеВозврата = "РегистрБухгалтерии";
 ИначеЕсли Метаданные.РегистрыНакопления.Содержит(мДвижение) Тогда
  ЗначениеВозврата = "РегистрНакопления";
 ИначеЕсли Метаданные.РегистрыРасчета.Содержит(мДвижение) Тогда
  ЗначениеВозврата = "РегистрРасчета";
 ИначеЕсли Метаданные.РегистрыСведений.Содержит(мДвижение) Тогда
  ЗначениеВозврата = "РегистрСведений";
 КонецЕсли;
 
 Возврат ЗначениеВозврата;
  
КонецФункции // ОпределитьВидРегистра()

&НаСервереБезКонтекста
Функция ПолучитьТекстXGML()

 ТаблицаРегистров = ПолучитьТаблицуРегистров();
 
 СводнаяТаблица = ТаблицаРегистров.Скопировать(, "Регистр, ВидРегистра");
 СводнаяТаблица.Свернуть("Регистр, ВидРегистра");
 
 ТекстЗапроса = "";
 Для Каждого СтрокаТаблицы Из СводнаяТаблица Цикл
  
  ТекстЗапроса = ТекстЗапроса + ?(ПустаяСтрока(ТекстЗапроса), "", "
  | ОБЪЕДИНИТЬ ВСЕ
  |") + ПолучитьТекстЗапроса(СтрокаТаблицы.Регистр, СтрокаТаблицы.ВидРегистра);
  
 КонецЦикла;
 
 Если ПустаяСтрока(ТекстЗапроса) Тогда
  Возврат Неопределено;
 КонецЕсли;
 
 ТекстЗапроса = ТекстЗапроса + "
 |ИТОГИ
 | СУММА(КоличествоДокументов)
 |ПО
 | ВидРегистра,
 | ИмяРегистра";
 
 Запрос = Новый Запрос(ТекстЗапроса);
 
 Возврат ОбработататьРезультатЗапроса(Запрос.Выполнить());
 
КонецФункции// ТекстXGML()

&НаСервереБезКонтекста
Функция ОбработататьРезультатЗапроса(Результат)

 Если Результат.Пустой() Тогда
  Возврат Неопределено;
 КонецЕсли;
 
 КэшРегистраторы = Новый Соответствие;
 
 ЗаписьXML = Новый ЗаписьXML;
 ЗаписьXML.УстановитьСтроку();
 ЗаписьXML.ЗаписатьОбъявлениеXML();
 
 ЗаписьXML.ЗаписатьНачалоЭлемента("section");
 ЗаписьXML.ЗаписатьАтрибут("name", "xgml");
 
 ЗаписьXML.ЗаписатьНачалоЭлемента("section");
 ЗаписьXML.ЗаписатьАтрибут("name", "graph");
 
 СоздатьГруппуXGML(ЗаписьXML, "Документы", "Документы");
 
 ВидыРегистров = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
 Пока ВидыРегистров.Следующий() Цикл
  
  ВидРегистра = ВидыРегистров.ВидРегистра;
  СоздатьГруппуXGML(ЗаписьXML, ВидРегистра);
  
  Регистры = ВидыРегистров.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
  Пока Регистры.Следующий() Цикл
   
   ИмяРегистра = Регистры.ИмяРегистра;
   СоздатьЭлементXGML(ЗаписьXML, ИмяРегистра, ВидРегистра);
   
   Регистраторы = Регистры.Выбрать();
   Пока Регистраторы.Следующий() Цикл
    ВидДокумента = Строка(Регистраторы.ВидДокумента);
    Если НЕ ЗначениеЗаполнено(КэшРегистраторы[ВидДокумента]) Тогда
     СоздатьЭлементXGML(ЗаписьXML, ВидДокумента, "Документы");
     КэшРегистраторы.Вставить(ВидДокумента, Истина);
    КонецЕсли;
    
    СоздатьСвязьXGML(ЗаписьXML, ВидДокумента, ИмяРегистра);
    
   КонецЦикла;
   
  КонецЦикла;
  
 КонецЦикла;
 
 ЗаписьXML.ЗаписатьКонецЭлемента(); //graph
 
 ЗаписьXML.ЗаписатьКонецЭлемента(); //xgml
 
 Возврат ЗаписьXML.Закрыть();

КонецФункции // ОбработататьРезультатЗапроса()

&НаСервереБезКонтекста
Процедура СоздатьСвязьXGML(ЗаписьXML, Начало, Конец)

 ЗаписатьНачалоСекцииXGML(ЗаписьXML, "edge");
 ЗаписатьАтрибутXGML(ЗаписьXML, "source", "string", Начало);
 ЗаписатьАтрибутXGML(ЗаписьXML, "target", "string", Конец);
 
 ЗаписатьНачалоСекцииXGML(ЗаписьXML, "graphics");
 ЗаписатьАтрибутXGML(ЗаписьXML, "fill", "string", "#000000");
 ЗаписатьАтрибутXGML(ЗаписьXML, "targetArrow", "string", "standard");
 ЗаписьXML.ЗаписатьКонецЭлемента(); //graphics
 
 ЗаписьXML.ЗаписатьКонецЭлемента(); //edge

КонецПроцедуры // СоздатьСвязьXGML()

&НаСервереБезКонтекста
Процедура СоздатьЭлементXGML(ЗаписьXML, ИмяЭлемента, ГруппаЭлемента = "");

 ЗаписатьНачалоСекцииXGML(ЗаписьXML, "node");
 
 ЗаписатьАтрибутXGML(ЗаписьXML, "id", "string", ИмяЭлемента);
 ЗаписатьАтрибутXGML(ЗаписьXML, "label", "string", ИмяЭлемента);
 
 ЗаписатьНачалоСекцииXGML(ЗаписьXML, "graphics");
 ЗаписатьАтрибутXGML(ЗаписьXML, "type", "string", "rectangle");
 ЗаписатьАтрибутXGML(ЗаписьXML, "fill", "string", "#FFCC00");
 ЗаписатьАтрибутXGML(ЗаписьXML, "outline", "string", "#000000");
 ЗаписьXML.ЗаписатьКонецЭлемента(); //graphics
 
 ЗаписатьНачалоСекцииXGML(ЗаписьXML, "LabelGraphics");
 ЗаписатьАтрибутXGML(ЗаписьXML, "text", "String", ИмяЭлемента);
 ЗаписатьАтрибутXGML(ЗаписьXML, "fontSize", "int", "12");
 ЗаписьXML.ЗаписатьКонецЭлемента(); //LabelGraphics
 
 Если НЕ ПустаяСтрока(ГруппаЭлемента) Тогда
  ЗаписатьАтрибутXGML(ЗаписьXML, "gid", "String", ГруппаЭлемента);
 КонецЕсли;
 
 ЗаписьXML.ЗаписатьКонецЭлемента(); //node

КонецПроцедуры // СоздатьЭлементXGML()

&НаСервереБезКонтекста
Процедура СоздатьГруппуXGML(ЗаписьXML, ИмяГруппы, ЗаголовокГруппы = "")

 Если ПустаяСтрока(ЗаголовокГруппы) Тогда
  ЗаголовокГруппы = ИмяГруппы;
 КонецЕсли;
 
 ЗаписатьНачалоСекцииXGML(ЗаписьXML, "node");
 
 ЗаписатьАтрибутXGML(ЗаписьXML, "id", "string", ИмяГруппы);
 ЗаписатьАтрибутXGML(ЗаписьXML, "label", "string", ЗаголовокГруппы);
 
 ЗаписатьНачалоСекцииXGML(ЗаписьXML, "graphics");
 ЗаписатьАтрибутXGML(ЗаписьXML, "type", "string", "roundrectangle");
 ЗаписатьАтрибутXGML(ЗаписьXML, "fill", "string", "#F5F5F5");
 ЗаписатьАтрибутXGML(ЗаписьXML, "outline", "string", "#000000");
 ЗаписьXML.ЗаписатьКонецЭлемента(); //graphics
 
 ЗаписатьНачалоСекцииXGML(ЗаписьXML, "LabelGraphics");
 ЗаписатьАтрибутXGML(ЗаписьXML, "text", "String", ЗаголовокГруппы);
 ЗаписатьАтрибутXGML(ЗаписьXML, "fill", "String", "#EBEBEB");
 ЗаписатьАтрибутXGML(ЗаписьXML, "fontSize", "int", "14");
 ЗаписатьАтрибутXGML(ЗаписьXML, "anchor", "String", "t");
 ЗаписьXML.ЗаписатьКонецЭлемента(); //LabelGraphics
 
 ЗаписатьАтрибутXGML(ЗаписьXML, "isGroup", "boolean", "true");
 
 ЗаписьXML.ЗаписатьКонецЭлемента(); //node

КонецПроцедуры // СоздатьГруппуXGML()

&НаСервереБезКонтекста
Процедура ЗаписатьНачалоСекцииXGML(ЗаписьXML, name)

 ЗаписьXML.ЗаписатьНачалоЭлемента("section");
 ЗаписьXML.ЗаписатьАтрибут("name", name);

КонецПроцедуры // ЗаписатьНачалоСекции()

&НаСервереБезКонтекста
Процедура ЗаписатьАтрибутXGML(ЗаписьXML, key, type, text)
 
 ЗаписьXML.ЗаписатьНачалоЭлемента("attribute");
 ЗаписьXML.ЗаписатьАтрибут("key", key);
 ЗаписьXML.ЗаписатьАтрибут("type", type);
 ЗаписьXML.ЗаписатьТекст(text);
 ЗаписьXML.ЗаписатьКонецЭлемента(); //attribute
 
КонецПроцедуры // ЗаписатьАтрибутXGML()

&НаСервереБезКонтекста
Функция ПолучитьТекстЗапроса(ИмяРегистра, ВидРегистра)
 
 ВидРегистраСтрока = """" + ВидРегистра + """";
 ИмяРегистраСтрока = """" + ИмяРегистра + """";
 
 ТекстЗапроса = 
 "ВЫБРАТЬ
 | ТИПЗНАЧЕНИЯ(Движения.Регистратор) КАК ВидДокумента,
 | " + ВидРегистраСтрока+ " КАК ВидРегистра,
 | " + ИмяРегистраСтрока+ " КАК ИмяРегистра,
 | КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Движения.Регистратор) КАК КоличествоДокументов
 |ИЗ
 | "+ВидРегистра+"."+ИмяРегистра+" КАК Движения
 |
 |СГРУППИРОВАТЬ ПО
 | ТИПЗНАЧЕНИЯ(Движения.Регистратор)";
 
 Возврат ТекстЗапроса;
 
КонецФункции

&НаКлиенте
Процедура Сформировать(Команда)
 
 ТекстXGML = ПолучитьТекстXGML();
 
КонецПроцедуры

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

Теперь открываем этот файл в yEd. Изначально все объекты будут свалены в одну кучу, поскольку при генерации в 1С координаты не присваивались. Поэтому сделаем в yEd пару преобразований. Для начала выполним увеличение размеров объектов до ширины текста. Эта команда находится в меню Tools -> Fit Node to Label. Теперь выполним расстановку объектов с помощью меню Layout. Для создания нижеприведенной картинки я выполнил команду Hierarchical и немного поигрался с параметрами.


Так же, приятным плюсом оказалось, что yEd распарсил диаграмму УПП 1.3 (размер 3 Мб, 367 объектов, 922 связи). Конечно, при таком количестве данных схема получилась объемной и перегруженной данными. Но, как демонстрация возможностей программы, вполне подойдет. И конечно же, сама обработка КартаДвижений прилагается.

4 комментария:

  1. Очень интересная тема, с движениями у Вас все здорово. Есть ли какие наработки для отображения остальных метаданных конфигурации 1С в UML?

    ОтветитьУдалить
    Ответы
    1. Привет, нет, пока что необходимости в подобных разработках не возникало.

      Удалить
    2. У меня вот возникла :-) Есть чужая конфигурация, нужно быстро освоить. Ищу инструмент визуализации объектов метаданных 1С (диаграмму классов банальную). И не нахожу ничего подобного в природе. И что удивительно, это никому и не нужно.

      Удалить
    3. Список ПО https://en.wikipedia.org/wiki/List_of_UML_tools. Больше всех подходит, по моему мнению, Modelio, free, с возможностью импорта модели из XML, есть автогенерация диаграмм классов. Остается только написать адекватный экспорт )

      Удалить