diff --git a/lib/utils.ts b/lib/utils.ts index 23a6291..e280eaf 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -57,6 +57,7 @@ export function formatXML(xml: string, indent: string = " "): string { * Efficiently converts a potentially incomplete XML string to a legal XML string by closing any open tags properly. * Additionally, if an tag does not have an mxGeometry child (e.g. ), * it removes that tag from the output. + * Also removes orphaned elements that aren't inside or don't have proper 'as' attribute. * @param xmlString The potentially incomplete XML string * @returns A legal XML string with properly closed tags and removed incomplete mxCell elements. */ @@ -69,10 +70,34 @@ export function convertToLegalXml(xmlString: string): string { while ((match = regex.exec(xmlString)) !== null) { // match[0] contains the entire matched mxCell block + let cellContent = match[0] + + // Remove orphaned elements that are directly inside + // without an 'as' attribute (like as="sourcePoint", as="targetPoint") + // and not inside + // These cause "Could not add object mxPoint" errors in draw.io + // First check if there's an - if so, keep all mxPoints inside it + const hasArrayPoints = //.test(cellContent) + if (!hasArrayPoints) { + // Remove mxPoint elements without 'as' attribute + cellContent = cellContent.replace( + /]*\/>/g, + (pointMatch) => { + // Keep if it has an 'as' attribute + if (/\sas=/.test(pointMatch)) { + return pointMatch + } + // Remove orphaned mxPoint + return "" + }, + ) + } + // Indent each line of the matched block for readability. - const formatted = match[0] + const formatted = cellContent .split("\n") .map((line) => " " + line.trim()) + .filter((line) => line.trim()) // Remove empty lines from removed mxPoints .join("\n") result += formatted + "\n" } @@ -587,6 +612,33 @@ export function validateMxCellStructure(xml: string): string | null { return `Invalid XML: Found edges with invalid source/target references (${invalidConnections.slice(0, 3).join(", ")}). Edge source and target must reference existing cell IDs. Please regenerate the diagram with valid edge connections.` } + // Check for orphaned mxPoint elements (not inside and without 'as' attribute) + // These cause "Could not add object mxPoint" errors in draw.io + const allMxPoints = doc.querySelectorAll("mxPoint") + const orphanedMxPoints: string[] = [] + allMxPoints.forEach((point) => { + const hasAsAttr = point.hasAttribute("as") + const parentIsArray = + point.parentElement?.tagName === "Array" && + point.parentElement?.getAttribute("as") === "points" + + if (!hasAsAttr && !parentIsArray) { + // Find the parent mxCell to report which edge has the problem + let parent = point.parentElement + while (parent && parent.tagName !== "mxCell") { + parent = parent.parentElement + } + const cellId = parent?.getAttribute("id") || "unknown" + if (!orphanedMxPoints.includes(cellId)) { + orphanedMxPoints.push(cellId) + } + } + }) + + if (orphanedMxPoints.length > 0) { + return `Invalid XML: Found orphaned mxPoint elements in cells (${orphanedMxPoints.slice(0, 3).join(", ")}). mxPoint elements must either have an 'as' attribute (e.g., as="sourcePoint") or be inside . For edge waypoints, use: . Please fix the mxPoint structure.` + } + return null }