{"record":{"id":"3664dcd4dea6dc39","repo":"iOfficeAI/OfficeCLI","slug":"drawing-group-hyperlink-entries-require-non-empty","errorCode":null,"errorMessage":"drawing-group hyperlink entries require non-empty Id and Target.","messagePattern":"drawing-group hyperlink entries require non-empty Id and Target\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Handlers/Excel/ExcelHandler.Add.cs","lineNumber":1177,"sourceCode":"                    groupDrawingsPart.WorksheetDrawing = new XDR.WorksheetDrawing();\n                    groupDrawingsPart.WorksheetDrawing.Save();\n                }\n                var groupSheet = GetSheet(groupWorksheet);\n                if (groupSheet.GetFirstChild<SpreadsheetDrawing>() == null)\n                {\n                    var drawingRelId = groupWorksheet.GetIdOfPart(groupDrawingsPart);\n                    groupSheet.Append(new SpreadsheetDrawing { Id = drawingRelId });\n                    SaveWorksheet(groupWorksheet);\n                }\n\n                // Relationship IDs are scoped to the destination drawing part.\n                // Create fresh IDs (avoids collisions with pictures/charts\n                // emitted earlier), then rewrite every r:id/r:embed/r:link in\n                // the verbatim group anchor that referenced the source ID.\n                foreach (var hyperlink in groupHyperlinks)\n                {\n                    if (string.IsNullOrEmpty(hyperlink.Id) || string.IsNullOrEmpty(hyperlink.Target))\n                        throw new ArgumentException(\n                            \"drawing-group hyperlink entries require non-empty Id and Target.\");\n                    var uri = new Uri(hyperlink.Target, UriKind.RelativeOrAbsolute);\n                    var replayRel = groupDrawingsPart.AddHyperlinkRelationship(\n                        uri, hyperlink.IsExternal);\n                    RemapDrawingRelationshipId(groupAnchor, hyperlink.Id, replayRel.Id);\n                }\n\n                groupDrawingsPart.WorksheetDrawing.AppendChild(groupAnchor);\n                groupDrawingsPart.WorksheetDrawing.Save();\n                var groupIndex = groupDrawingsPart.WorksheetDrawing\n                    .Elements<XDR.TwoCellAnchor>()\n                    .Count(a => a.GetFirstChild<XDR.GroupShape>() != null);\n                return (\"group\", $\"/{groupSheetName}/group[{groupIndex}]\");\n            }\n\n            case \"chartex\":\n            {\n                // Extended (cx:) chart carrier for dump→batch round-trip.","sourceCodeStart":1159,"sourceCodeEnd":1195,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Handlers/Excel/ExcelHandler.Add.cs#L1159-L1195","documentation":"After the hyperlinks carrier decoded successfully, at least one entry has an empty Id or Target. The handler needs both because it calls AddHyperlinkRelationship(Target) and then RemapDrawingRelationshipId(anchor, hyperlink.Id, ...) to rewrite the source rId in the verbatim anchor. An empty value would create a useless relationship and/or a no-op remap.","triggerScenarios":"A decoded entry whose base64 decoded to an empty string for Id or Target — e.g. the source anchor had a hyperlink with no r:id, or the carrier was authored with empty base64 (\" , ,1\").","commonSituations":"Source drawing contained a dangling hyperlink element with missing attributes; a dump that emitted a hyperlink before its relationship was resolved; hand-written carrier with placeholder empty fields.","solutions":["Filter out hyperlinks with empty Id or Target before encoding the carrier.","Re-dump so only resolved hyperlinks (with both r:id and Target) are emitted.","If the source truly lacks a Target, drop that hyperlink entry — it cannot be replayed."],"exampleFix":"// before — empty Target slips through\nvar specs = new List<DumpDrawingHyperlinkSpec>{\n    new(){ Id=\"rId2\", Target=\"\", IsExternal=true }};\nprops[\"hyperlinks\"] = EncodeDumpDrawingHyperlinks(specs);\n// after — drop incomplete entries before encoding\nprops[\"hyperlinks\"] = EncodeDumpDrawingHyperlinks(\n    specs.Where(h => !string.IsNullOrEmpty(h.Id) && !string.IsNullOrEmpty(h.Target)));","handlingStrategy":"validation","validationCode":"var specs = DecodeDumpDrawingHyperlinks(props.GetValueOrDefault(\"hyperlinks\") ?? \"\");\nif (specs.Any(h => string.IsNullOrEmpty(h.Id) || string.IsNullOrEmpty(h.Target)))\n    /* drop incomplete entries before encoding */","typeGuard":"static bool HyperlinksAllComplete(string encoded) =>\n    DecodeDumpDrawingHyperlinks(encoded).All(h =>\n        !string.IsNullOrEmpty(h.Id) && !string.IsNullOrEmpty(h.Target));","tryCatchPattern":"try { handler.AddPart(parent, \"drawing-group\", props); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"non-empty Id and Target\"))\n{ /* filter specs, re-encode, retry */ }","preventionTips":["Filter out empty-Id/Target hyperlinks before encoding.","Re-dump so only resolved hyperlinks are emitted.","Treat dangling hyperlinks (no r:id/Target) as data errors to drop."],"tags":["excel","openxml","add-part","drawing-group","hyperlinks","validation"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}