{"record":{"id":"eaed0d4f5394ac30","repo":"iOfficeAI/OfficeCLI","slug":"cell-reference-cellref-replace-n-n-repl","errorCode":null,"errorMessage":"Cell reference '{cellRef.Replace(\"\\n\", \"\\\\n\").Replace(\"\\r\", \"\\\\r\")}' contains invalid control characters. Expected a clean cell address like 'A1' or 'B2'.","messagePattern":"Cell reference '(.+?)' contains invalid control characters\\. Expected a clean cell address like 'A1' or 'B2'\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Handlers/Excel/ExcelHandler.Query.cs","lineNumber":455,"sourceCode":"                sheetNode.Format[\"colBreaks\"] = string.Join(\",\", cbreaks);\n            }\n\n            if (depth > 0)\n            {\n                sheetNode.Children = GetSheetChildNodes(sheetNameFromPath, data, depth, worksheet);\n                // Children omit value-less empty cells/rows (issue #149);\n                // reflect the actual listed count, not the raw row count.\n                sheetNode.ChildCount = sheetNode.Children.Count;\n            }\n            return sheetNode;\n        }\n\n        // BUG-R41-F2: reject cell reference segments that contain control characters\n        // (e.g. \\n, \\r, \\t). Without this check, \"A1\\n\" passes the cell-ref regex\n        // (Regex `$` matches before trailing \\n in .NET) and resolves to a ghost cell.\n        var cellRef = segments[1];\n        if (cellRef.Any(c => c < ' ' && c != '\\t' || c == '\\x7f'))\n            throw new ArgumentException(\n                $\"Cell reference '{cellRef.Replace(\"\\n\", \"\\\\n\").Replace(\"\\r\", \"\\\\r\")}' contains invalid control characters. \" +\n                $\"Expected a clean cell address like 'A1' or 'B2'.\");\n\n        // Page break path: /Sheet1/rowbreak[N] or /Sheet1/colbreak[N]\n        var rbMatch = Regex.Match(cellRef, @\"^rowbreak\\[(\\d+)\\]$\", RegexOptions.IgnoreCase);\n        if (rbMatch.Success)\n        {\n            var rbIdx = int.Parse(rbMatch.Groups[1].Value);\n            var rowBreaks = GetSheet(worksheet).GetFirstChild<RowBreaks>();\n            var breaks = rowBreaks?.Elements<Break>().ToList() ?? new();\n            if (rbIdx < 1 || rbIdx > breaks.Count)\n                throw new ArgumentException($\"Row break index {rbIdx} out of range (1-{breaks.Count})\");\n            var brk = breaks[rbIdx - 1];\n            var rbNode = new DocumentNode\n            {\n                Path = path, Type = \"rowbreak\",\n                Format = { [\"row\"] = brk.Id?.Value ?? 0u, [\"manual\"] = brk.ManualPageBreak?.Value ?? false }\n            };","sourceCodeStart":437,"sourceCodeEnd":473,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Handlers/Excel/ExcelHandler.Query.cs#L437-L473","documentation":"Thrown when the cell-reference segment (the part after the sheet, e.g. A1 in /Sheet1/A1) contains ASCII control characters (except tab) or DEL (0x7F). The guard exists because .NET regex '$' anchors before a trailing \\n, so a value like 'A1\\n' would otherwise pass the cell-ref check and silently resolve to a non-existent 'ghost' cell (BUG-R41-F2). Rejecting it surfaces the bad input instead of returning phantom data.","triggerScenarios":"Calling Get with a cell segment carrying \\n/\\r/\\t from unsanitized user input, a clipboard paste, or string concatenation — e.g. handler.Get(\"/Sheet1/A1\\n\") or /Sheet1/B2\\r. Reading addresses from a DB/CSV column that has trailing newlines.","commonSituations":"Pasting an address from a cell that includes a newline. Interpolating values read from another source without trimming. Cross-platform line endings (\\r\\n) leaking into a path segment.","solutions":["Sanitize the path before Get: strip chars in \\x00-\\x08, \\x0B, \\x0C, \\x0E-\\x1F, \\x7F (keep tab only if intended).",".Trim() the cell segment and reject empty results.","Validate with a clean cell-ref regex (^[A-Za-z]{1,3}[0-9]+$) before calling Get."],"exampleFix":"// before\nvar cell = handler.Get(\"/Sheet1/\" + addr); // addr = \"A1\\n\" -> throws\n\n// after\nvar clean = Regex.Replace(addr, @\"[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]\", \"\").Trim();\nvar cell = handler.Get($\"/Sheet1/{clean}\");","handlingStrategy":"validation","validationCode":"// strip control chars (except tab) + DEL before Get\nvar clean = Regex.Replace(path, @\"[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]\", \"\");\nif (clean != path)\n    throw new ArgumentException(\"Path contained control characters\");\nreturn handler.Get(clean);","typeGuard":"static bool HasControlChars(string cellRef) =>\n    cellRef.Any(c => (c < ' ' && c != '\\t') || c == '\\x7f');","tryCatchPattern":null,"preventionTips":["Sanitize every path built from user/clipboard/DB input before Get.",".Trim() cell segments and reject empties.","Validate cell refs against ^[A-Za-z]{1,3}[0-9]+$ before use."],"tags":["excel","sanitization","cell-reference","control-characters","regex"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}