fix: add orphaned mxPoint validation and cleanup (#130)

- Add validation for orphaned mxPoint elements in validateMxCellStructure()
- Add cleanup of orphaned mxPoint elements in convertToLegalXml()
- Orphaned mxPoints cause 'Could not add object mxPoint' errors in draw.io
- mxPoint elements must have 'as' attribute or be inside <Array as="points">

Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
This commit is contained in:
Dayuan Jiang
2025-12-07 00:40:19 +09:00
committed by GitHub
parent a415c46b66
commit 2fac6323f0

View File

@@ -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. * Efficiently converts a potentially incomplete XML string to a legal XML string by closing any open tags properly.
* Additionally, if an <mxCell> tag does not have an mxGeometry child (e.g. <mxCell id="3">), * Additionally, if an <mxCell> tag does not have an mxGeometry child (e.g. <mxCell id="3">),
* it removes that tag from the output. * it removes that tag from the output.
* Also removes orphaned <mxPoint> elements that aren't inside <Array> or don't have proper 'as' attribute.
* @param xmlString The potentially incomplete XML string * @param xmlString The potentially incomplete XML string
* @returns A legal XML string with properly closed tags and removed incomplete mxCell elements. * @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) { while ((match = regex.exec(xmlString)) !== null) {
// match[0] contains the entire matched mxCell block // match[0] contains the entire matched mxCell block
let cellContent = match[0]
// Remove orphaned <mxPoint> elements that are directly inside <mxGeometry>
// without an 'as' attribute (like as="sourcePoint", as="targetPoint")
// and not inside <Array as="points">
// These cause "Could not add object mxPoint" errors in draw.io
// First check if there's an <Array as="points"> - if so, keep all mxPoints inside it
const hasArrayPoints = /<Array\s+as="points">/.test(cellContent)
if (!hasArrayPoints) {
// Remove mxPoint elements without 'as' attribute
cellContent = cellContent.replace(
/<mxPoint\b[^>]*\/>/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. // Indent each line of the matched block for readability.
const formatted = match[0] const formatted = cellContent
.split("\n") .split("\n")
.map((line) => " " + line.trim()) .map((line) => " " + line.trim())
.filter((line) => line.trim()) // Remove empty lines from removed mxPoints
.join("\n") .join("\n")
result += formatted + "\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.` 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 <Array as="points"> 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 <Array as="points">. For edge waypoints, use: <Array as="points"><mxPoint x="..." y="..."/></Array>. Please fix the mxPoint structure.`
}
return null return null
} }