From 1263d4f3d58471b79502b24f8c307f08979853b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 03:27:55 +0000 Subject: [PATCH] Trim only ASCII white space under AllowLeadingWhite and AllowTrailingWhite [patch] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TrimAllowedWhite called ReadOnlySpan.TrimStart()/TrimEnd(), which strip everything char.IsWhiteSpace accepts: NBSP, U+202F, U+3000, NEL, LS, PS and the other Unicode space separators. NumberStyles allows only U+0009-U+000D and U+0020, and decimal, double and int reject the rest, so PreciseNumber accepted input every BCL numeric type refuses (for example a stray trailing fr-FR group separator, "1 ", under Integer). The trim now uses that explicit set. Fixes ktsu-dev/PreciseNumber#181 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW4JrXevVTQZvqzLBX1 --- .../PreciseNumberParseCultureTests.cs | 37 +++++++++++++++++++ PreciseNumber/PreciseNumber.cs | 11 +++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/PreciseNumber.Test/PreciseNumberParseCultureTests.cs b/PreciseNumber.Test/PreciseNumberParseCultureTests.cs index ac429ee..24e238b 100644 --- a/PreciseNumber.Test/PreciseNumberParseCultureTests.cs +++ b/PreciseNumber.Test/PreciseNumberParseCultureTests.cs @@ -241,4 +241,41 @@ public void Parse_RejectsATypedSpaceWhenTheStyleDoesNotAllowThousands() Assert.IsFalse(PreciseNumber.TryParse("1 234", NumberStyles.Float, NarrowSpaceGroups, out _)); Assert.IsFalse(PreciseNumber.TryParse("1 234", NumberStyles.Float, NoBreakSpaceGroups, out _)); } + + [TestMethod] + [DataRow("\u00A05", NumberStyles.Integer)] + [DataRow("5\u3000", NumberStyles.AllowTrailingWhite)] + [DataRow("\u00855", NumberStyles.Float)] + [DataRow("5\u2028", NumberStyles.Float)] + public void Parse_RejectsNonAsciiWhiteSpaceAroundTheNumber(string input, NumberStyles style) + { + Assert.IsFalse(decimal.TryParse(input, style, CultureInfo.InvariantCulture, out _), "The BCL should reject this input too."); + Assert.IsFalse(PreciseNumber.TryParse(input, style, CultureInfo.InvariantCulture, out _)); + Assert.ThrowsExactly(() => PreciseNumber.Parse(input, style, CultureInfo.InvariantCulture)); + } + + [TestMethod] + public void Parse_RejectsATrailingNarrowSpaceGroupSeparatorWhenTheStyleDoesNotAllowThousands() + { + // Shaped like fr-FR, whose group separator is U+202F: a stray one after the digits is not white space. + Assert.IsFalse(decimal.TryParse("1\u202F", NumberStyles.Integer, NarrowSpaceGroups, out _), "The BCL should reject this input too."); + Assert.IsFalse(PreciseNumber.TryParse("1\u202F", NumberStyles.Integer, NarrowSpaceGroups, out _)); + Assert.ThrowsExactly(() => PreciseNumber.Parse("1\u202F", NumberStyles.Integer, NarrowSpaceGroups)); + } + + [TestMethod] + [DataRow(" ")] + [DataRow("\t")] + [DataRow("\r")] + [DataRow("\n")] + [DataRow("\v")] + [DataRow("\f")] + public void Parse_TrimsAsciiWhiteSpaceAroundTheNumber(string white) + { + string input = white + "5" + white; + + Assert.IsTrue(PreciseNumber.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out PreciseNumber result)); + Assert.AreEqual(P("5"), result); + Assert.AreEqual(P("5"), PreciseNumber.Parse(input, NumberStyles.Integer, CultureInfo.InvariantCulture)); + } } diff --git a/PreciseNumber/PreciseNumber.cs b/PreciseNumber/PreciseNumber.cs index 060127d..927d098 100644 --- a/PreciseNumber/PreciseNumber.cs +++ b/PreciseNumber/PreciseNumber.cs @@ -1391,17 +1391,24 @@ private static PreciseNumber FromParsedParts(ReadOnlySpan digits, int deci return new((int)exponent, significand); } + /// + /// The white space that and + /// permit: U+0009 to U+000D and U+0020, as for the BCL numeric types. Other Unicode white space, such as a no-break + /// space or NEL, is not trimmed. + /// + private const string AllowedWhite = " \t\n\v\f\r"; + /// Trims the leading and trailing white space that allows. private static ReadOnlySpan TrimAllowedWhite(ReadOnlySpan s, NumberStyles style) { if (style.HasFlag(NumberStyles.AllowLeadingWhite)) { - s = s.TrimStart(); + s = s.TrimStart(AllowedWhite.AsSpan()); } if (style.HasFlag(NumberStyles.AllowTrailingWhite)) { - s = s.TrimEnd(); + s = s.TrimEnd(AllowedWhite.AsSpan()); } return s;