Как программно преобразовать BPMN XML в редактируемый Visio (.vsdx/.vdx) с помощью C# или Python? [закрыто]C#

Место общения программистов C#
Ответить
Anonymous
 Как программно преобразовать BPMN XML в редактируемый Visio (.vsdx/.vdx) с помощью C# или Python? [закрыто]

Сообщение Anonymous »

Я создал файл BPMN из XML и теперь хочу преобразовать его в файл Visio ( или .vdx), чтобы блок-схему можно было открыть и редактировать в Microsoft Visio.
Я обнаружил, что Bizagi Modeler может выполнить это преобразование вручную, но мне нужно автоматизировать процесс программно — в идеале с использованием C#.
Я пробовал реализовать это на C#, но сгенерированный мною файл .vdx выходит пусто.

🧩 Что мне нужно
  • Преобразовать XML BPMN (пример ниже) в редактируемый файл Visio
  • Сохранять точно тот же рабочий процесс и макет
  • В идеале добиться этого без использования Bizagi или другого графического интерфейса инструменты

Код: Выделить всё







id-1258cd0e-8bc5-4c52-85b8-567500708524
id-25164739-c8de-4780-8bca-9a02c2340979


id-25164739-c8de-4780-8bca-9a02c2340979
id-c9a4b533-5a8b-4a30-b9c4-bf300bd9f6e4


id-1258cd0e-8bc5-4c52-85b8-567500708524







<p class="m-0 mb-[8px] relative last:mb-0"><br></p>
id-205183a7-69dd-425b-af32-cbfcd0b7c740


<p class="m-0 mb-[8px] relative last:mb-0"><br></p>
id-c9a4b533-5a8b-4a30-b9c4-bf300bd9f6e4
id-88ed032f-2ad4-4688-987e-77b41bb11ede
id-205183a7-69dd-425b-af32-cbfcd0b7c740


<p class="m-0 mb-[8px] relative last:mb-0"><br></p>
id-88ed032f-2ad4-4688-987e-77b41bb11ede








































































Диаграмма:

Изображение
Код, который я пробовал (C# с использованием Aspose.Diagram)

Код: Выделить всё

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml;
using Aspose.Diagram;
using Aspose.Diagram.Manipulation;

class Program
{
// Basic fallback mapping for stencil mode
static readonly Dictionary BasicShapeMap = new(StringComparer.OrdinalIgnoreCase)
{
{ "startEvent", "Circle" },
{ "endEvent", "Circle" },
{ "exclusiveGateway", "Diamond" },
{ "parallelGateway", "Diamond" },
{ "task", "Rectangle" },
{ "userTask", "Rectangle" },
{ "serviceTask", "Rectangle" },
{ "participant", "Rectangle" } // pools as rectangles (fallback)
};

// Optional: if you also load a BPMN stencil, map BPMN tags to real BPMN masters here.
static readonly Dictionary BpmnStencilMap = new(StringComparer.OrdinalIgnoreCase)
{
{ "startEvent", "Start Event" },
{ "endEvent", "End Event" },
{ "task", "Task" },
{ "userTask", "User Task" },
{ "serviceTask", "Service Task" },
{ "exclusiveGateway", "Gateway Exclusive" },
{ "parallelGateway", "Gateway Parallel" },
{ "participant", "Pool / Lane"  } // actual name may vary by stencil
};

static int Main(string[] args)
{
try
{
var o = ParseArgs(args);
if (o == null)
{
PrintHelp();
return 1;
}

if (o.Mode.Equals("svg", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrWhiteSpace(o.SvgPath) || string.IsNullOrWhiteSpace(o.OutPath))
{
Console.WriteLine("Missing --svg or --out for svg mode.");
return 1;
}
SvgToVisio(o.SvgPath, o.OutPath);
Console.WriteLine($"✅ SVG embedded → {o.OutPath}");
return 0;
}

// Stencil mode
if (string.IsNullOrWhiteSpace(o.BpmnPath) || string.IsNullOrWhiteSpace(o.OutPath) || string.IsNullOrWhiteSpace(o.StencilPath))
{
Console.WriteLine("Missing --bpmn, --out, or --stencil for stencil mode.");
return 1;
}

Console.WriteLine("[1/2] Parsing BPMN…");
var (nodes, edges, shapes, flows) = ParseBpmn(o.BpmnPath!);

Console.WriteLine("[2/2] Building Visio (stencil)...");
BpmnToVisioWithStencil(
basicStencilPath: o.StencilPath!,
bpmnStencilPath: o.BpmnStencilPath,
nodes: nodes,
edges: edges,
shapes: shapes,
flows: flows,
outPath: o.OutPath!
);

Console.WriteLine($"✅ Done → {o.OutPath}");
return 0;
}
catch (Exception ex)
{
Console.WriteLine("❌ Error: " + ex);
return 2;
}
}

// =========================
// MODE 2: SVG → Visio page
// =========================
static void SvgToVisio(string svgPath, string outPath)
{
if (!File.Exists(svgPath))
throw new FileNotFoundException("SVG file not found", svgPath);

var d = new Diagram();
var page = d.Pages[0];

// Place the SVG as a vector object on the page.
// Aspose.Diagram 25.10.0 supports AddShape with a stream for images (including SVG).
using (var fs = File.OpenRead(svgPath))
{
double x = 1, y = 1, w = 800, h = 600; // adjust as needed; or parse viewBox from SVG
long sid = page.AddShape(x, y, w, h, fs);
var shp = page.Shapes.GetShape(sid);
shp.Name = "BPMN_SVG";
}

d.Save(outPath, SaveFileFormat.Vsdx);
}

// ==========================================
// MODE 1: BPMN + Stencil (editable shapes)
// ==========================================
static void BpmnToVisioWithStencil(
string basicStencilPath,
string? bpmnStencilPath,
Dictionary nodes,
List edges,
Dictionary shapes,
Dictionary  flows,
string outPath)
{
if (!File.Exists(basicStencilPath))
throw new FileNotFoundException("Basic stencil not found", basicStencilPath);

// Load a diagram from the basic stencil so its masters exist (Rectangle/Circle/Diamond/Dynamic connector).
var d = new Diagram(basicStencilPath);

// Optionally merge a BPMN stencil (masters copied into current diagram)
if (!string.IsNullOrWhiteSpace(bpmnStencilPath))
{
if (!File.Exists(bpmnStencilPath))
throw new FileNotFoundException("BPMN stencil not found", bpmnStencilPath);

var bpmn = new Diagram(bpmnStencilPath);
CopyMastersIfMissing(bpmn, d);
}

var page = d.Pages[0];
var idToShape = new Dictionary();

// Resolve a usable master name for a BPMN type.
string ResolveMasterName(string bpmnType)
{
// Prefer BPMN stencil name if present in diagram masters
if (BpmnStencilMap.TryGetValue(bpmnType, out var bpmnName) &&
TryFindMaster(d, bpmnName, out var foundBpmn))
{
return foundBpmn;
}

// Fallback to basic shape master
if (BasicShapeMap.TryGetValue(bpmnType, out var basicName) &&
TryFindMaster(d, basicName, out var foundBasic))
{
return foundBasic;
}

// Fallback to Rectangle if available
if (TryFindMaster(d, "Rectangle", out var rect))
return rect;

// Last resort: any master present
foreach (Master m in d.Masters)
return m.Name;

throw new DiagramException("No masters available in loaded stencil(s).");
}

// Add node shapes
foreach (var kv in nodes)
{
string id = kv.Key;
var (name, type) = kv.Value;
if (!shapes.TryGetValue(id, out var box)) continue;

var (x, y, w, h) = box;
string masterName = ResolveMasterName(type);

long sid = page.AddShape(x, y, w, h, masterName); // IMPORTANT: string master name (Aspose 25.10.0)
var shp = page.Shapes.GetShape(sid);
shp.Text.Value.Add(new Txt(string.IsNullOrWhiteSpace(name) ? type : name));
idToShape[id] = shp;
}

// Ensure we can create connectors
string dynName = TryFindMaster(d, "Dynamic connector", out var dyn) ? dyn
: throw new DiagramException("Dynamic connector master not found in the loaded stencil(s).");

// Add connectors with DI waypoints
foreach (var (eid, src, tgt) in edges)
{
if (!idToShape.TryGetValue(src, out var sShape)) continue;
if (!idToShape.TryGetValue(tgt, out var tShape)) continue;

long connId = page.AddShape(0, 0, 0, 0, dynName);
var connector = page.Shapes.GetShape(connId);

if (flows.TryGetValue(eid, out var pts) && pts.Count > 0)
{
foreach (var (px, py) in pts)
{
var c = new Connection();
c.X.Value = px;
c.Y.Value = py;
connector.Connections.Add(c);
}
}

// Connect using shapes &  enum (Aspose 25.10.0)
page.ConnectShapesViaConnector(
sShape, ConnectionPointPlace.Right,
tShape, ConnectionPointPlace.Left,
connector
);
}

d.Save(outPath, SaveFileFormat.Vsdx);
}

// Copy masters from 'src' into 'dst' if name missing
static void CopyMastersIfMissing(Diagram src, Diagram dst)
{
foreach (Master m in src.Masters)
{
if (!TryFindMaster(dst, m.Name, out _))
{
dst.Masters.Add(m);
}
}
}

// Try to find a master by name in current diagram (Aspose 25.10.0 requires manual scan).
static bool TryFindMaster(Diagram d, string name, out string actualName)
{
foreach (Master m in d.Masters)
{
if (string.Equals(m.Name, name, StringComparison.OrdinalIgnoreCase))
{
actualName = m.Name;
return true;
}
}
actualName = null!;
return false;
}

// =========================
// BPMN parse with DI
// =========================
static (Dictionary,
List,
Dictionary,
Dictionary)
ParseBpmn(string file)
{
var doc = new XmlDocument();
doc.Load(file);

var ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("bpmn", "http://www.omg.org/spec/BPMN/20100524/MODEL");
ns.AddNamespace("bpmndi", "http://www.omg.org/spec/BPMN/20100524/DI");
ns.AddNamespace("dc", "http://www.omg.org/spec/DD/20100524/DC");
ns.AddNamespace("di", "http://www.omg.org/spec/DD/20100524/DI");

var nodes = new Dictionary();
var edges = new List();
var shapes = new Dictionary();
var flows = new Dictionary();

foreach (XmlNode s in doc.SelectNodes("//bpmndi:BPMNShape", ns)!)
{
string bpmnId = s.Attributes!["bpmnElement"]!.Value;
var b = s.SelectSingleNode("dc:Bounds", ns)!;

double x = double.Parse(b.Attributes!["x"]!.Value, CultureInfo.InvariantCulture);
double y = double.Parse(b.Attributes!["y"]!.Value, CultureInfo.InvariantCulture);
double w = double.Parse(b.Attributes!["width"]!.Value, CultureInfo.InvariantCulture);
double h = double.Parse(b.Attributes!["height"]!.Value, CultureInfo.InvariantCulture);

shapes[bpmnId] = (x, y, w, h);
}

foreach (XmlNode e in doc.SelectNodes("//bpmndi:BPMNEdge", ns)!)
{
string bpmnId = e.Attributes!["bpmnElement"]!.Value;
var pts = new List();
foreach (XmlNode wp in e.SelectNodes("di:waypoint", ns)!)
{
double x = double.Parse(wp.Attributes!["x"]!.Value, CultureInfo.InvariantCulture);
double y = double.Parse(wp.Attributes!["y"]!.Value, CultureInfo.InvariantCulture);
pts.Add((x, y));
}
flows[bpmnId] = pts;
}

foreach (XmlNode n in doc.SelectNodes("//bpmn:*", ns)!)
{
string tag = n.LocalName;

if (BasicShapeMap.ContainsKey(tag) || tag.Contains("task", StringComparison.OrdinalIgnoreCase))
{
string id = n.Attributes!["id"]!.Value;
string? name = n.Attributes!["name"]?.Value?.Trim();
if (string.IsNullOrEmpty(name)) name = tag;
nodes[id] = (name!,  tag);
}

if (tag == "sequenceFlow")
{
edges.Add((
n.Attributes!["id"]!.Value,
n.Attributes!["sourceRef"]!.Value,
n.Attributes!["targetRef"]!.Value
));
}
}

return (nodes, edges, shapes, flows);
}

// =========================
// CLI parsing & help
// =========================
class Options
{
public string Mode = "stencil"; // stencil | svg
public string? BpmnPath;
public string? OutPath;
public string? StencilPath;     // REQUIRED for stencil mode
public string? BpmnStencilPath; // OPTIONAL for real BPMN masters
public string? SvgPath;         // for svg mode
}

static Options? ParseArgs(string[] args)
{
if (args.Length == 0) return null;
var o = new Options();

for (int i = 0; i < args.Length; i++)
{
string a = args[i];
if (a == "--mode" && i + 1 < args.Length) o.Mode = args[++i];
else if (a == "--bpmn" && i + 1 < args.Length) o.BpmnPath = args[++i];
else if (a == "--out" && i + 1 < args.Length) o.OutPath = args[++i];
else if (a == "--stencil" && i + 1 < args.Length) o.StencilPath = args[++i];
else if (a == "--bpmnStencil" && i + 1 < args.Length) o.BpmnStencilPath = args[++i];
else if (a == "--svg" && i + 1 < args.Length) o.SvgPath = args[++i];
}

return o;
}

static void PrintHelp()
{
Console.WriteLine(@"
Bpmn2Visio (Aspose.Diagram 25.10.0)

USAGE:
# Mode 1 — Stencil (editable shapes)
Bpmn2Visio.exe --mode stencil --bpmn input.bpmn --out output.vsdx --stencil ""C:\...\BASIC_U.VSSX"" [--bpmnStencil ""C:\...\BPMN.vssx""]

# Mode 2 — SVG (pixel-perfect)
Bpmn2Visio.exe --mode svg --svg diagram.svg --out output.vsdx

NOTES:
- In Stencil mode you MUST pass a stencil that includes 'Rectangle', 'Circle', 'Diamond', and 'Dynamic connector' (e.g., BASIC_U.VSSX).
- Optionally add a BPMN stencil to get true BPMN icons.
- In SVG mode, export SVG from Camunda Modeler (File → Export → SVG) and pass it via --svg. The SVG is embedded as a single vector object.
");
}
}
Однако созданный файл .vsdx открывается в Visio но совершенно пуст — не отображаются никакие фигуры или соединители.
Теперь мой вопрос:

Как правильно преобразовать XML-файл BPMN в редактируемую диаграмму Visio программным способом на C#?
Цель: создать Файл Visio, который выглядит и ведет себя так же, как экспорт BPMN Bizagi — редактируемый и правильно отформатированный.
Необходима помощь, как и каким путем я могу достичь желаемой цели.

Подробнее здесь: https://stackoverflow.com/questions/798 ... ly-using-c
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»