Привет, Хабр! Меня зовут Алексей Грохотов, я разрабатываю продукт Сфера.Архитектура в ИТ‑холдинге Т1. Перед нашей командой стояла задача перенести документы из Orbus iServer в Сфера.Архитектуру. Iserver — это набор инструментов для описания, поддержки и трансформации архитектуры предприятия. Он в значительной степени интегрирован с Microsoft Office, например, все схемы в этом инструментарии создаются в Visio.

Я должен был проанализировать схемы Visio и извлечь необходимую информацию из этих документов. Объекты, соответствующие «прямоугольничкам и стрелочкам» Visio, уже хранились у нас в базе. Мне нужно было соотнести их с фигурами и стрелками схемы, записать для этих объектов геометрическое и текстовое содержание фигур, а также некоторые их специфические свойства. Ещё нужно было определить порты — «стыковочные места» по периметрам фигур, к которым присоединяются стрелки, а также найти надписи у стрелок и фигур. И после этого сохранить в базу данных всю найденную информацию.

Забегая вперёд, покажу результат успешного переноса в Сфера.Архитектуру схемы, нарисованной в Visio.

До:

После:

Первые подходы

В первую очередь, я поискал в интернете имеющиеся решения. Нашёл Apache POI и Aspose.Diagram. POI хорошо работал с файлами Excel, но крайне ограниченно — с документами Visio: мне удалось извлечь только название схемы, имя создавшего и некоторые другие, не особо полезные данные. 

Aspose — платное решение, предоставляющее API для создания, парсинга и преобразования документов Visio в собственные форматы приложений. Он нам не подошёл, во‑первых, из‑за того, что он платный, а во‑вторых, не хотелось привязываться к стороннему ПО. К слову, у этого продукта есть очень хороший форум, на котором служба поддержки отвечает на вопросы пользователей. Эта информация очень помогла мне, когда я пытался в массе одинаковых XML‑тэгов найти ответ «как же это здесь реализовано». 

Тогда я поискал по Хабру и с удивлением обнаружил, что статей про парсинг схем Visio нет. Видимо, до недавнего времени никто не занимался такой бесполезной ерундой уникальной задачей. Отчасти это, но в большей степени затраченное время заставило меня описать свои действия и результат. Может, этот опыт поможет кому-то сэкономить своё время.

Структура документа Visio

Ранние версии Visio сохраняли схемы в формате VSD. Это бинарный формат, таких документов у нас было мало, и с ними я не работал. Сейчас же используется формат VSDX, который, как и другие файлы Microsoft Office, представляет собой ZIP‑архив. В нём содержатся XML‑файлы, описывающие содержимое документа.

Для статьи я создал небольшой документ example.vsdx, представляющий собой организационную схему с гендиром, руководителем и тремя разработчиками. 

*Схема Visio с руководителем и разработчиками
*Схема Visio с руководителем и разработчиками

Разархивируем его, чтобы посмотреть структуру файлов:

\example\docProps\app.xml
\example\docProps\core.xml
\example\docProps\custom.xml
\example\docProps\thumbnail.emf
\example\visio\document.xml
\example\visio\masters\master1.xml
\example\visio\masters\master10.xml
\example\visio\masters\master11.xml
\example\visio\masters\master12.xml
\example\visio\masters\master13.xml
\example\visio\masters\master14.xml
\example\visio\masters\master15.xml
\example\visio\masters\master2.xml
\example\visio\masters\master3.xml
\example\visio\masters\master4.xml
\example\visio\masters\master5.xml
\example\visio\masters\master6.xml
\example\visio\masters\master7.xml
\example\visio\masters\master8.xml
\example\visio\masters\master9.xml
\example\visio\masters\masters.xml
\example\visio\masters\_rels\master1.xml.rels
\example\visio\masters\_rels\master2.xml.rels
\example\visio\masters\_rels\master3.xml.rels
\example\visio\masters\_rels\master4.xml.rels
\example\visio\masters\_rels\master5.xml.rels
\example\visio\masters\_rels\master6.xml.rels
\example\visio\masters\_rels\master7.xml.rels
\example\visio\masters\_rels\masters.xml.rels
\example\visio\media\image1.jpeg
\example\visio\media\image2.bmp
\example\visio\pages\page1.xml
\example\visio\pages\pages.xml
\example\visio\pages\_rels\page1.xml.rels
\example\visio\pages\_rels\pages.xml.rels
\example\visio\solutions\solution1.xml
\example\visio\solutions\solutions.xml
\example\visio\solutions\_rels\solutions.xml.rels
\example\visio\theme\theme1.xml
\example\visio\windows.xml
\example\visio\_rels\document.xml.rels
\example\[Content_Types].xml
\example\_rels\.rels 

Нас интересует папка \example\visio\pages\. В ней есть файл pages.xml, содержащий описание страниц документа, и файл page1.xml, где описаны элементы на странице, фигуры и их связи друг с другом. Если файл Visio — многостраничный документ, то в папке pages будут содержаться файлы page1.xml, page2.xml, page3.xml и так далее. 

Структура файла страницы

Рассмотрим подробнее структуру файла page1.xml. В нём есть элемент PageContents, у которого есть дочерние элементы Shapes и Connects. Shapes описывают свойства фигур и соединителей («стрелочек»), их местоположение на листе, размеры, содержащийся в них текст и прочее. В упрощенном виде структура файла выглядит так:

<?xml version='1.0' encoding='utf-8' ?>
<PageContents xmlns='http://schemas.microsoft.com/office/visio/2012/main'
    xmlns:r='http://schemas.openxmlformats.org/officeDocument/2006/relationships' xml:space='preserve'>
    <Shapes>
        <Shape ID='31' NameU='Dynamic connector' Name='Динамический соединитель' Type='Shape' Master='15' UniqueID='{104EEDCC-FAF4-437C-B860-C89095023E2A}'>
            <Cell N='TxtPinX' V='0.09842519462108615'/>
            <Cell N='TxtPinY' V='-0.5270669460296633'/>
            <!-- еще несколько элементов Cell -->
            <Section N='Geometry' IX='0'>
                <Row T='MoveTo' IX='1'>
                    <Cell N='X' V='0.09842519685039353'/>
                </Row>
                <Row T='LineTo' IX='2'>
                    <Cell N='X' V='0.09842519685039353'/>
                    <Cell N='Y' V='-1.054133857858449'/>
                </Row>
                <Row T='LineTo' IX='3' Del='1'/>
                <Row T='LineTo' IX='4' Del='1'/>
            </Section>
        </Shape>
        <Shape ID='32' Type='Shape' Master='15' UniqueID='{736D6637-EACE-43D3-B3F1-6087F0A60B11}'>
            <!-- содержимое фигуры, опустил для краткости -->
        </Shape>
    </Shapes>
    <Connects>
        <Connect FromSheet='54' FromCell='EndX' FromPart='12' ToSheet='43' ToCell='Connections.Top.X' ToPart='100'/>
        <!-- еще соединители, опустил для краткости -->
    </Connects>
</PageContents>

У каждой фигуры есть свои атрибуты. В первую очередь, меня интересуют ID — идентификатор фигуры внутри родительского элемента PageContents, и UniqueID — уникальный идентификатор UUID, или, как называет его Microsoft, GUID. Фигура также содержит дочерние элементы Cell, Section, Text и вложенные элементы Shapes. Элемент Section содержит элементы Row. Здесь меня интересует ячейки с именами PinX и PinY, определяющие местоположение фигуры на листе, а также элемент Text. Все размеры в фигурах Microsoft Visio указываются в дюймах, я умножал извлечённые значения на некоторый коэффициент, чтобы получить размеры в пикселях.

Описание соединителей

Соединители описывают, какие фигуры соединяются друг с другом. Рассмотрим в качестве примера два первых элемента Connect с атрибутом FromSheet, равным 54:

<Connects>
    <!-- Строка показывает, что фигруа 54 (FromSheet='54') соединяется с фигурой 43 (ToSheet='43') -->
    <Connect FromSheet='54' FromCell='EndX' FromPart='12' ToSheet='43' ToCell='Connections.Top.X' ToPart='100'/>

    <!-- Эта строка показывает, что та же фигруа 54 также соединяется с фигурой 11 -->
    <Connect FromSheet='54' FromCell='BeginX' FromPart='9' ToSheet='11' ToCell='Connections.Bottom.X' ToPart='103'/>

    <Connect FromSheet='53' FromCell='EndX' FromPart='12' ToSheet='33' ToCell='Connections.Top.X' ToPart='100'/>
    <Connect FromSheet='53' FromCell='BeginX' FromPart='9' ToSheet='11' ToCell='Connections.Left.X' ToPart='101'/>
    <Connect FromSheet='32' FromCell='EndX' FromPart='12' ToSheet='21' ToCell='Connections.Top.X' ToPart='100'/>
    <Connect FromSheet='32' FromCell='BeginX' FromPart='9' ToSheet='11' ToCell='Connections.Right.X' ToPart='102'/>
    <Connect FromSheet='31' FromCell='EndX' FromPart='12' ToSheet='11' ToCell='Connections.Float.X' ToPart='104'/>
    <Connect FromSheet='31' FromCell='BeginX' FromPart='9' ToSheet='1' ToCell='Connections.Bottom.X' ToPart='103'/>
</Connects>

Атрибут FromSheet указывает на номер фигуры, содержащей описание соединителя. Из этого описания можно извлечь координаты соединителя (ячейки с именами PinX и PinY), а также координаты изгибов этого соединителя (секция Geometry, ряды MoveTo и, LineTo). Забегая вперёд, скажу, что мне не удалось перевести полученные значения в нужные мне единицы — стрелки‑соединители по этим значениям отображались криво, и мы не стали использовать эти координаты. 

Атрибут ToSheet указывает на номера фигур, соединяемых этим соединителем‑стрелкой. В Connects есть два элемента Connect с номером 54, у первого атрибут ToSheet равен 43, у второго — 11. Получаем такую картину: фигура 54 описывает стрелку на схеме, соединяющую две фигуры с номерами 43 и 11. Это «Разработчик ПО Семён Шарпов» и «Руководитель разработки». Ниже я привёл содержимое этих фигур, вырезав некоторые элементы, которые я не использовал. Из этих фигур я беру информацию о содержимом фигуры (элемент Text), её размерах (ячейки Height и Width, в этом примере отсутствуют) и о расположении на схеме (ячейки PinX и PinY). Также из них можно извлечь информацию о расположении текста относительно страницы (TxtPinX и TxtPinY) и фигуры (TxtLocPinX и TxtLocPinY).

Описание фигуры

Вот элемент с номером 54, описывающий соединитель:

        <Shape ID='54' NameU='Dynamic connector.54' Name='Динамический соединитель.54' Type='Shape' Master='15' UniqueID='{63A888AC-0A5D-4E4B-9738-168F9973DA8D}'>
<! -- 'PinX', 'PinY' - координаты центра фигуры-стрелки -->
            <Cell N='PinX' V='6.628444882' F='Inh'/>
            <Cell N='PinY' V='4.21579724416911' F='Inh'/>


<! -- 'BeginX', 'BeginY', 'EndX', 'EndY' - координаты начала и конца стрелки -->
            <Cell N='BeginX' V='6.628444882' F='PAR(PNT(Sheet.11!Connections.Bottom.X,Sheet.11!Connections.Bottom.Y))'/>
            <Cell N='BeginY' V='4.927657480141551' F='PAR(PNT(Sheet.11!Connections.Bottom.X,Sheet.11!Connections.Bottom.Y))'/>
            <Cell N='EndX' V='6.628444882' F='PAR(PNT(Sheet.43!Connections.Top.X,Sheet.43!Connections.Top.Y))'/>
            <Cell N='EndY' V='3.50393700819667' F='PAR(PNT(Sheet.43!Connections.Top.X,Sheet.43!Connections.Top.Y))'/>

<! -- PinX, PinY - координаты центра фигуры-стрелки. MoveTo, LineTo – координаты изгибов -->
            <Section N='Geometry' IX='0'>
                <Row T='MoveTo' IX='1'>
                    <Cell N='X' V='-0.09842519685039353'/>
                </Row>
                <Row T='LineTo' IX='2'>
                    <Cell N='X' V='-0.09842519685039441'/>
                    <Cell N='Y' V='-1.423720471944882'/>
                </Row>
            </Section>
        </Shape>

Элемент 43, описывающий фигуру «Разработчик ПО»:

<Shape ID='43' NameU='Position Belt.43' Name='Пояс должности.43' Type='Group' Master='5' UniqueID='{670A7028-8CC9-4BD9-9531-9AA1200B6158}'>

<! -- 'PinX', 'PinY' - координаты центра фигуры «Разработчик ПО» -->
    <Cell N='PinX' V='6.628444882' F='PNTX(LOCTOPAR(User.PageLoc,ThePage!PageWidth,Width))'/>
    <Cell N='PinY' V='3.06643700819667' F='PNTY(LOCTOPAR(User.PageLoc,ThePage!PageWidth,Width))'/>
    <Text>
        <cp IX='0'/>
        Разработчик ПО
    </Text>
    <Shapes>
        <Shape ID='44' Type='Group' MasterShape='6' UniqueID='{E8AEAC34-763B-41E8-8BBE-C1FCEE857515}'>

<! -- 'Height ' - высота фигуры  -->
            <Cell N='Height' V='0.1333828247070313' F='Inh'/>

<! -- 'TxtPinY' - координата текстового блока по оси Y  -->
            <Cell N='TxtPinY' V='0.06669141235351563' F='Inh'/>
            <Cell N='TxtHeight' V='0.1333828247070313' F='Inh'/>

<! -- текстовый блок с содержимым  -->
            <Text>
                <cp IX='0'/>
                Семен Шарпов
            </Text>
        </Shape>
    </Shapes>
</Shape>

Элемент 11, описывающий фигуру «Руководитель разработки»:

<Shape ID='11' NameU='Manager Belt' Name='Лента менеджера' Type='Group' Master='4' UniqueID='{E4A2BCB3-9C78-425F-8D4B-C6C4654D0F6E}'>
    <Cell N='PinX' V='6.628444882' F='PNTX(LOCTOPAR(User.PageLoc,ThePage!PageWidth,Width))'/>
    <Cell N='PinY' V='5.365157480141551' F='PNTY(LOCTOPAR(User.PageLoc,ThePage!PageWidth,Width))'/>
    <Text>
        <cp IX='0'/>
        Руководитель разработки
    </Text>
    <Shapes>
        <Shape ID='12' Type='Group' MasterShape='6' UniqueID='{87F8D211-514F-4119-8F90-05FC62DD1193}'>
            <Cell N='Height' V='0.1333828247070313' F='Inh'/>
            <Cell N='LocPinY' V='0.06669141235351563' F='Inh'/>
            <Cell N='TxtPinY' V='0.06669141235351563' F='Inh'/>
            <Cell N='TxtHeight' V='0.1333828247070313' F='Inh'/>
            <Cell N='TxtLocPinY' V='0.06669141235351563' F='Inh'/>
            <Text>
                <cp IX='0'/>
                Ольга Петрова
            </Text>
        </Shape>
    </Shapes>
</Shape>

Парсинг

Открываем ZIP-файл

Как уже говорил, файл с расширением *.vsdx представляет собой ZIP-архив. Прежде, чем начать парсинг, распакуем архив, создав временный файл. Не забудьте удалить созданный файл после работы с ним:

private File extractXmlFile(ZipInputStream zipInputStream) throws IOException {
    File tempFile = File.createTempFile("temp", ".xml"); // создаем временный файл

    try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile)) {
        byte[] buffer = new byte[1024];
        int bytesRead;

        while ((bytesRead = zipInputStream.read(buffer)) != -1) { // читаем данные из стрима, пока не достигнем конца файла
            fileOutputStream.write(buffer, 0, bytesRead);
        }
    }

    return tempFile;
}

DOM-объекты

У любого элемента в XML-документе есть свои атрибуты, такие как ID у фигур, или имена и значения (N, V) у ячеек. Создадим абстрактный класс Dom с полем attributes. Также создадим его классы-наследники, соответствующие элементам XML-документа Shape, Section, Cell, Row, элементам верхнего уровня Masters и Pages

public abstract class Dom {
    private Map<String, String> attributes
}

public class Pages extends Dom {
    private List<Page> pageList;
}

public class Shape extends Dom {
    private List<Section> sections;
    private List<Cell> cells;
    private Text text;
    private List<Connect> connects;
    private List<Shape> shapes;
}

public class Section extends Dom {
    private List<Row> rows;
}

public class Text extends Dom {
    private String contents;
    private CharRowProperties cp;
}

При парсинге я использовал классы для обработки XML-документов DocumentBuilderFactory и DocumentBuilder из пакета javax.xml.parsers и интерфейсы Entity, Node и NodeList из пакета org.w3c.dom, представляющие «составные части» XML-документа.

Пример парсинга

Рассмотрим для примера парсинг файла page1.xml, в нём содержится максимальное количество полезной информации о схеме.

public Dom parseXml(File xmlFile) throws ParserConfigurationException, SAXException, IOException {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // часть, общая для парсинга любого XML-файла Visio
    DocumentBuilder builder = factory.newDocumentBuilder();
    Document document = builder.parse(xmlFile);
    Element root = document.getDocumentElement();

    // Здесь мы можем определить, какой файл пришел на вход в метод parseXml(), запросив имя внешнего элемента структуры.
    // Под каждый XML-файл Visio я создал отдельный метод, чтобы не перебирать всевозможные элементы структуры, а использовать только те, которые имеются в этом файле.
    switch (root.getNodeName()) {
        case "Pages" -> {
            return parsePages(root); // метод для получения данных из элемента Pages (файл pages.xml)
        }
        case "PageContents" -> {
            return parsePageContents(root); // метод для получения данных из элемента PageContents (файлы page1.xml, page2.xml и т.д.)
        }
        case "Masters" -> {
            return parseMasters(root); // метод для получения данных из элемента Masters (файл masters.xml)
        }
    }
    // ...
}

Так как XML-файл Visio содержит много повторяющихся структур со схожими элементами, создадим параметризированный метод, возвращающий список нужных нам элементов. Я частенько сталкивался с ситуацией, когда «здесь должен быть этот элемент вот прям железно», а его не было. Поэтому почти всё проверяю на null.

private <T extends Dom> List<T> populateElements(String tagName, Element parent, Class<T> domClass) {
    List<T> domList = new ArrayList<>();

    if (parent == null) { 
        return domList;
    }

    NodeList nodes = parent.getChildNodes();
        for (int i = 0; i < nodes.getLength(); i++) {
            try {
                var element = (Element) nodes.item(i);

                if (tagName.equals(element.getTagName())) { // проверяем по имени, что элемент в списке - тот, что нам нужен
                    T dom = domClass.getDeclaredConstructor().newInstance();
                    dom.setAttributes(getAttributes(element));
                    domList.add(dom);
                }
            } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
                // пишем сообщение в лог
            }
        }
        return domList;
    }

Например, для получения соединителей я вызываю это метод со следующими параметрами:

populateElements(
"Connect",  // Название искомого элемента
(Element) root.getElementsByTagName("Connects").item(0),  // родительский элемент
Connect.class // DOM-класс, коллекцию из которых возвращает метод
);

Напомню, что у элемента PageContents есть два дочерних типа элементов: фигуры и соединители. Нахожу первые с помощью нашего параметризированного populateElements(). Для нахождения вложенных фигур использую отдельный метод populateShapes().

private PageContents parsePageContents(Element root) {
    // находим соединители (элементы структуры, которые описывают, как фигуры соединяются друг с другом) 
    var connects = populateElements(
            "Connect",
            (Element) root.getElementsByTagName("Connects").item(0),
            Connect.class
        );

    // фигуры могут иметь вложенные фигуры, потому ищу вложенные через отдельный метод populateShapes()
    var rootNodes = root.getChildNodes();
    List<Shape> outerShapes = new ArrayList<>();

    for (int i = 0; i < rootNodes.getLength(); i++) {
        if ("Shapes".equals(rootNodes.item(i).getNodeName())) {
            outerShapes = populateShapes((Element) rootNodes.item(i));
        }
    }

    // возвращаю родительский элемент файла page1.xml
    var pageContents = new PageContents();
    pageContents.setShapes(outerShapes);
    pageContents.setConnects(connects);
    return pageContents;
}

Подобным же образом нахожу у фигуры содержимое секций и атрибутов (getSections(), getAttributes()). Содержимое методов не привожу, действуем по тому же алгоритму: ищем элементы по тегу, итерируем по найденному, в случае совпадения имени добавляем элемент к заранее созданному списку, возвращаем список элементов.

private List<Shape> populateShapes(Element element) {
    List<Shape> shapes = new ArrayList<>();
    List<Shape> innerShapes = new ArrayList<>();
    var nodeList = element.getChildNodes();

    for (int i = 0; i < nodeList.getLength(); i++) {
        var shapeElement = (Element) nodeList.item(i);
        var shape = new Shape();
        List<Cell> cells = populateElements("Cell", shapeElement, Cell.class);

        var children = shapeElement.getChildNodes();
        for (int j = 0; j < children.getLength(); j++) {
            if ("Shapes".equals(children.item(j).getNodeName())) {
                innerShapes = populateShapes((Element) children.item(j)); // рекурсивно нахожу вложенные шейпы
            }
        }

        shape.setSections(getSections(shapeElement));
        setShapeText(shapeElement, shape);
        shape.setCells(cells);
        shape.setAttributes(getAttributes(shapeElement));
        shape.setShapes(innerShapes);
        shapes.add(shape);
    }

    return shapes;
}

Метод для поиска текста внутри фигуры. Я также сохранял значения свойств символов (character properties, cp), на практике они нигде нам не потребовались:

private void setShapeText(Element shapeElement, Shape shape) {
    var textElement = (Element) shapeElement.getElementsByTagName("Text").item(0);

    if (textElement != null) {
        var text = new Text();
        var contents = textElement.getTextContent();
        var cpNode = textElement.getElementsByTagName("cp").item(0);
        var cpElement = (Element) cpNode;
        var cp = new CharRowProperties();
        cp.setAttributes(getAttributes(cpElement));
        text.setCp(cp);
        text.setContents(contents);
        shape.setText(text);
    }
}

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

Запись в базу данных

Для записи я использовал уже имеющийся сервис интеграции. Полученные в результате парсинга параметры сопоставлял с соответствующим DTO и с помощью клиента feign отправлял на соответствующие endpoint-ы сервиса.

Заключение

Я рассказал о своём опыте парсинга информации из схем Visio в базу данных. Подходящих готовых решений не нашёл, поэтому сделал своё: сопоставлял разные элементы с объектами в базе и извлекал связанную с ними информацию. Если хотите дополнить или покритиковать, добро пожаловать в комментарии :)

Комментарии (4)


  1. Surrogate
    15.10.2025 06:41

    Сейчас же используется формат VSDX, который, как и другие файлы Microsoft Office, представляет собой ZIP‑архив. В нём содержатся XML‑файлы, описывающие содержимое документа.

    Очень давно (в 2019 году) была написана статья Из Visio в Excel через Power Query, на основе ветки обсуждения в русскоязычном форуме MS Visio.


  1. itGuevara
    15.10.2025 06:41

    Делал так:
    1 на входе список файлов visio, на выходе заполненный excel со всеми объектами на схемах visio со ссылками на каждый объект (на файл, лист, объект).

    2 excel поочередно сканирует макросом (VBA) каждый файл (открывает его, чтобы не изучать формат файла) и заполняет табличку excel, включая shp.id, shp.name, shp.master и т.п.

    3 в итоге получаем репозитарий объектов. Схемы в visio рисовались по корпоративному шаблону (VAD, EPC), поэтому получался хорошо структурируемый репозитарий (по shp.master понятен тип объекта: процесс, событие и т.п.), конечно нужно было убрать двойные \ тройные пробелы в названиях, спецсимволы и т.п. (провести очистку наименований).

    4 при сканировании автоматом добавлялась привязка к фигуре через штатную связку MS "excel-visio", что обеспечивало переход от строки в реестре - репозитарии к схеме (схемам), где он был найден.

    Полагаю, что такой путь (VBA) проще.


  1. Bagatur
    15.10.2025 06:41

    Мда... Какое-то время назад была другая задача - сгенерировать visio схему исходя из табличных данных. В visio есть готовый шаблон подобного, но только для организационной диаграммы. А вот чтобы, условно, отрисовать схему сетевых подключений исходя из условного ACL, взятого из файрволлов/роутера/L3 коммутатора - вот тут и споткнулись. Разрабов, чтобы такое на VBA пытаться реализовать, в нашем подразделении нет. А штатным разрабам в организации некогда. Пытался на PlantUML - да, неплохо, но только для схем с малой вложенностью. Не подошло, увы.


    1. itGuevara
      15.10.2025 06:41

      сгенерировать visio схему исходя из табличных данных.

      Делал подобное, но для схем VAD. В excel составляешь табличку, из нее формируешь фигуры нужного типа и коннектор между ними. Причем все это без координат (точнее у всех одна координата). Потом даешь штатную команду visio: "размести" (плюс уточнения слева - направо и т.п.) и visio сам координаты пересчитывает. Фактически тот же PlantUML, но из таблицы (отношения между объектами в табличном виде).
      Если говорить не про visio, то эта же задача решалась (два варианта в excel и js) в ВРМ. Смарт-инструменты «Таблица -> Схема». Там был промежуточный этап формирования dot (в visio он был не нужен). В PlantUML "под капотом" GraphViz (dot).