{"record":{"id":"6d61d264aee091b6","repo":"iOfficeAI/OfficeCLI","slug":"docprops-is-a-singleton-use-docprops-or-no-in","errorCode":null,"errorMessage":"docProps is a singleton; use /docProps or / (no index).","messagePattern":"docProps is a singleton; use /docProps or / \\(no index\\)\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"warning","filePath":"src/officecli/Handlers/Excel/ExcelHandler.Query.cs","lineNumber":230,"sourceCode":"                nrNode.Format[\"comment\"] = dn.Comment.Value;\n            if (dn.Function?.Value == true)\n                nrNode.Format[\"volatile\"] = true;\n\n            return nrNode;\n        }\n\n        // Parse path: /SheetName or /SheetName/A1 or /SheetName/A1:D10\n        var segments = path.TrimStart('/').Split('/', 2);\n        var sheetNameFromPath = segments[0];\n        // workbook is a singleton at the document root — reject an indexed\n        // /workbook[N] with a redirect rather than treating \"workbook[N]\" as a\n        // sheet name (which fires a misleading SheetNotFoundException). Mirrors\n        // the pptx notes[N]/theme[N] and docx watermark[N] redirects.\n        if (Regex.IsMatch(sheetNameFromPath, @\"^workbook\\[\\d+\\]$\", RegexOptions.IgnoreCase))\n            throw new ArgumentException(\"workbook is a singleton; use /workbook or / (no index).\");\n        // docProps is a document-level part, not a sheet — same redirect class.\n        if (Regex.IsMatch(sheetNameFromPath, @\"^docProps\\[\\d+\\]$\", RegexOptions.IgnoreCase))\n            throw new ArgumentException(\"docProps is a singleton; use /docProps or / (no index).\");\n        var worksheet = FindWorksheet(sheetNameFromPath);\n        if (worksheet == null)\n            throw SheetNotFoundException(sheetNameFromPath);\n        // CONSISTENCY(path-stability): if the path used sheet[N] / sheet[last()],\n        // rebuild the canonical path with the resolved sheet name so the returned\n        // node.Path reflects the actual sheet (matches Word's last() echo behavior).\n        var resolvedSheetName = ResolveSheetName(sheetNameFromPath);\n        if (!resolvedSheetName.Equals(sheetNameFromPath, StringComparison.Ordinal))\n        {\n            sheetNameFromPath = resolvedSheetName;\n            path = segments.Length == 1 ? $\"/{resolvedSheetName}\" : $\"/{resolvedSheetName}/{segments[1]}\";\n        }\n\n        var data = GetSheet(worksheet).GetFirstChild<SheetData>();\n        if (data == null)\n            return new DocumentNode { Path = path, Type = \"sheet\", Preview = \"(empty)\" };\n\n        if (segments.Length == 1)","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Handlers/Excel/ExcelHandler.Query.cs#L212-L248","documentation":"Thrown by ExcelHandler.Get when the first path segment is /docProps[N]. docProps (core/extended document properties) is a document-level singleton part with exactly one instance per workbook, so an index is meaningless. The guard redirects the caller to the canonical /docProps (or /) route instead of treating 'docProps[N]' as a sheet name, which would otherwise raise a misleading SheetNotFoundException. Mirrors the /workbook[N] singleton redirect.","triggerScenarios":"Calling handler.Get(\"/docProps[1]\"), Get(\"/docProps[2]\"), or any /docProps[N] on an opened .xlsx. Happens when a generic path builder appends [1] to every top-level node it discovers under /.","commonSituations":"Code that synthesizes element paths as name[index] for all root nodes. Copy-pasting a /sheet[1] or /chart[1] pattern onto document-level parts. Misreading the tree where docProps sits at the document root, not under a sheet.","solutions":["Drop the index: call handler.Get(\"/docProps\") (or Get(\"/\") for the whole workbook).","Special-case singleton root parts (docProps, workbook) in any generic path builder so they emit no index.","Pre-validate the path: regex ^docProps\\[\\d+\\]$ => rewrite to /docProps before calling Get."],"exampleFix":"// before\nvar props = handler.Get(\"/docProps[1]\"); // throws\n\n// after\nvar props = handler.Get(\"/docProps\");","handlingStrategy":"validation","validationCode":"// docProps/workbook are singletons — strip any [N] before Get\nvar head = path.TrimStart('/').Split('/', 2)[0];\nif (Regex.IsMatch(head, @\"^(?:workbook|docProps)\\[\\d+$\", RegexOptions.IgnoreCase))\n    path = \"/\" + head.Split('[')[0];\nreturn handler.Get(path);","typeGuard":"static bool IsIndexedSingleton(string path) =>\n    Regex.IsMatch(path.TrimStart('/').Split('/', 2)[0],\n        @\"^(?:workbook|docProps)\\[\\d+\\]$\", RegexOptions.IgnoreCase);","tryCatchPattern":null,"preventionTips":["Never append an index to root-level singleton parts (docProps, workbook).","When generating paths generically from the '/' tree, special-case singletons to emit no index.","Pre-validate the path shape before calling Get."],"tags":["excel","docprops","singleton","path-routing","argument-exception"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}