{"record":{"id":"d5174b6a6db31e96","repo":"iOfficeAI/OfficeCLI","slug":"invalid-midpoint-midpointstr-expected-a-numbe","errorCode":null,"errorMessage":"Invalid midPoint '{midPointStr}': expected a number, optionally as percentile:<n> / percent:<n> / num:<n> or '<n>%'.","messagePattern":"Invalid midPoint '(.+?)': expected a number, optionally as percentile:<n> / percent:<n> / num:<n> or '<n>%'\\.","errorType":"validation","errorClass":"System.ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Handlers/Excel/ExcelHandler.Add.Cf.cs","lineNumber":330,"sourceCode":"        {\n            midType = midRaw[..midColon].Trim().ToLowerInvariant() switch\n            {\n                \"percentile\" => ConditionalFormatValueObjectValues.Percentile,\n                \"percent\" => ConditionalFormatValueObjectValues.Percent,\n                \"num\" or \"number\" => ConditionalFormatValueObjectValues.Number,\n                var badKind => throw new ArgumentException(\n                    $\"Unknown midPoint kind '{badKind}'. Valid: percentile:<n>, percent:<n>, num:<n>, or a bare number (percentile).\")\n            };\n            midRaw = midRaw[(midColon + 1)..].Trim();\n        }\n        else if (midRaw.EndsWith('%'))\n        {\n            midType = ConditionalFormatValueObjectValues.Percent;\n            midRaw = midRaw[..^1].Trim();\n        }\n        if (!double.TryParse(midRaw, System.Globalization.NumberStyles.Float,\n                System.Globalization.CultureInfo.InvariantCulture, out _))\n            throw new ArgumentException(\n                $\"Invalid midPoint '{midPointStr}': expected a number, optionally as percentile:<n> / percent:<n> / num:<n> or '<n>%'.\");\n        var colorScale = new ColorScale();\n        colorScale.Append(new ConditionalFormatValueObject { Type = ConditionalFormatValueObjectValues.Min });\n        if (midColor != null)\n            colorScale.Append(new ConditionalFormatValueObject { Type = midType, Val = midRaw });\n        colorScale.Append(new ConditionalFormatValueObject { Type = ConditionalFormatValueObjectValues.Max });\n        colorScale.Append(new DocumentFormat.OpenXml.Spreadsheet.Color { Rgb = normalizedMinColor });\n        if (midColor != null)\n        {\n            var normalizedMidColor = ParseHelpers.NormalizeArgbColor(midColor);\n            colorScale.Append(new DocumentFormat.OpenXml.Spreadsheet.Color { Rgb = normalizedMidColor });\n        }\n        colorScale.Append(new DocumentFormat.OpenXml.Spreadsheet.Color { Rgb = normalizedMaxColor });\n\n        var csRule = new ConditionalFormattingRule\n        {\n            Type = ConditionalFormatValues.ColorScale,\n            Priority = NextCfPriority(GetSheet(csWorksheet))","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Handlers/Excel/ExcelHandler.Add.Cf.cs#L312-L348","documentation":"Thrown by AddColorScale after the midPoint kind prefix (or '%' suffix) is stripped: the remaining substring must parse as a double using invariant culture. This guards the <cfvo val=...> attribute so a non-numeric value never reaches the OOXML output, which would make Excel refuse the workbook. The original full midPoint string is echoed in the message for diagnosis.","triggerScenarios":"Calling Add with type=colorscale and a midpoint= whose payload (after prefix/suffix handling) is not a floating-point number. Examples: midpoint=percentile:fifty, midpoint=high, midpoint=num:, midpoint=50%% (double percent).","commonSituations":"Typo or stray characters in the number portion; locale confusion where a comma decimal separator is used instead of a period; passing an empty value after a valid prefix.","solutions":["Provide a numeric value: midpoint=50, midpoint=percentile:50, midpoint=percent:25, or midpoint=50%.","Use a period as the decimal separator (invariant culture parsing) — e.g. midpoint=12.5 not midpoint=12,5.","If omitting the midpoint, also omit the midcolor property so the rule becomes a 2-color scale."],"exampleFix":"// before: midpoint=percentile:fifty\nadd /Sheet1/A1:A10 colorscale midpoint=percentile:fifty\n// after: numeric value\nadd /Sheet1/A1:A10 colorscale midpoint=percentile:50","handlingStrategy":"validation","validationCode":"var mp = (properties.GetValueOrDefault(\"midpoint\") ?? properties.GetValueOrDefault(\"midPoint\") ?? \"50\").Trim();\nvar colon = mp.IndexOf(':');\nvar numPart = colon > 0 ? mp[(colon + 1)..].Trim() : (mp.EndsWith('%') ? mp[..^1].Trim() : mp);\nif (!double.TryParse(numPart, NumberStyles.Float, CultureInfo.InvariantCulture, out _))\n    throw new ArgumentException($\"midPoint '{mp}' numeric portion invalid.\");","typeGuard":"static bool IsValidMidPointNumber(string? s)\n{\n    if (s is null) return true;\n    var mp = s.Trim();\n    var colon = mp.IndexOf(':');\n    var part = colon > 0 ? mp[(colon + 1)..].Trim() : (mp.EndsWith('%') ? mp[..^1].Trim() : mp);\n    return double.TryParse(part, NumberStyles.Float, CultureInfo.InvariantCulture, out _);\n}","tryCatchPattern":"try { return Add(path, \"colorscale\", pos, props); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"Invalid midPoint\"))\n{ props[\"midpoint\"] = \"50\"; return Add(path, \"colorscale\", pos, props); }","preventionTips":["Use a period as the decimal separator (invariant culture).","Strip stray characters before passing the value.","Omit midpoint and midcolor together for a clean 2-color scale."],"tags":["excel","conditional-formatting","colorscale","numeric-validation","argument-validation"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}