import type { DiagramNode, DiagramEdge } from '@/types/network-diagram' // Maps draw.io shape identifiers (substrings of style) → our device slugs const DRAWIO_SHAPE_TO_SLUG: Array<[string, string]> = [ ['cisco.routers.router', 'router'], ['cisco.routers', 'router'], ['cisco.switches.layer_3_switch', 'switch'], ['cisco.switches.workgroup_switch', 'switch'], ['cisco.switches', 'switch'], ['cisco.firewalls', 'firewall'], ['cisco.servers', 'server'], ['cisco.computers_and_peripherals.laptop', 'laptop'], ['cisco.computers_and_peripherals.ip_phone', 'phone'], ['cisco.computers_and_peripherals.pc', 'workstation'], ['cisco.computers_and_peripherals.printer', 'printer'], ['cisco.misc.access_point', 'access-point'], ['cisco.misc.cloud', 'cloud'], ['cisco.storage', 'nas'], ['shape=router', 'router'], ['shape=server', 'server'], ['shape=firewall', 'firewall'], ['shape=cloud', 'cloud'], ] function styleToSlug(style: string): string { const lower = style.toLowerCase() for (const [pattern, slug] of DRAWIO_SHAPE_TO_SLUG) { if (lower.includes(pattern)) return slug } return 'server' } function isGroup(style: string): boolean { return style.includes('swimlane') || style.includes('container') || style.includes('group') } export interface DrawioImportResult { nodes: DiagramNode[] edges: DiagramEdge[] warnings: string[] } export function parseDrawioXml(xmlString: string): DrawioImportResult { const parser = new DOMParser() const doc = parser.parseFromString(xmlString, 'application/xml') const parseError = doc.querySelector('parsererror') if (parseError) { throw new Error('Invalid draw.io XML: ' + parseError.textContent?.slice(0, 200)) } const cells = Array.from(doc.querySelectorAll('mxCell')) const warnings: string[] = [] const nodes: DiagramNode[] = [] const edges: DiagramEdge[] = [] const geoMap = new Map() for (const cell of cells) { const geo = cell.querySelector('mxGeometry') if (geo) { geoMap.set(cell.getAttribute('id') ?? '', { x: parseFloat(geo.getAttribute('x') ?? '0'), y: parseFloat(geo.getAttribute('y') ?? '0'), width: parseFloat(geo.getAttribute('width') ?? '120'), height: parseFloat(geo.getAttribute('height') ?? '120'), }) } } const groupIds = new Set() for (const cell of cells) { const id = cell.getAttribute('id') ?? '' if (id === '0' || id === '1') continue const isEdge = cell.getAttribute('edge') === '1' const isVertex = cell.getAttribute('vertex') === '1' const style = cell.getAttribute('style') ?? '' const value = cell.getAttribute('value') ?? '' const parent = cell.getAttribute('parent') ?? '1' const geo = geoMap.get(id) if (isEdge) { const source = cell.getAttribute('source') ?? '' const target = cell.getAttribute('target') ?? '' if (!source || !target) { warnings.push(`Edge "${id}" skipped — missing source or target`) continue } edges.push({ id, source, target, label: value || null, connectionType: 'ethernet', speed: null, notes: null, routing: null, }) continue } if (isVertex && geo) { if (isGroup(style)) { groupIds.add(id) nodes.push({ id, type: 'subnet', label: value || 'Group', position: { x: geo.x, y: geo.y }, properties: { hostname: null, ip: null, subnet: null, vendor: null, model: null, role: null, vlan: null, notes: null, status: 'unknown', }, nodeType: 'group', style: { width: geo.width, height: geo.height }, }) } else { const slug = styleToSlug(style) const parentId = parent !== '1' && groupIds.has(parent) ? parent : undefined nodes.push({ id, type: slug, label: value || slug, position: { x: geo.x, y: geo.y }, properties: { hostname: null, ip: null, subnet: null, vendor: null, model: null, role: null, vlan: null, notes: null, status: 'unknown', }, ...(parentId ? { parentId } : {}), style: { width: geo.width, height: geo.height }, }) } } } if (nodes.length === 0) { warnings.push('No nodes were found in this draw.io file. Only basic shapes and Cisco stencil shapes are supported.') } return { nodes, edges, warnings } }