From e743dd71e11d562102283993b2828519a5eb8d77 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:30:58 +0000 Subject: [PATCH 1/2] Initial plan From de033200d7e937b338b757c7102328e31f974484 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:03:03 +0000 Subject: [PATCH 2/2] Rewrite ConvertToDecimal using string.Create with for loop; add tests Co-authored-by: EgorBo <523221+EgorBo@users.noreply.github.com> --- .../src/System/Xml/Xsl/Runtime/XslNumber.cs | 40 +++++++--------- .../XslCompilerTests.cs | 46 +++++++++++++++++++ 2 files changed, 63 insertions(+), 23 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XslNumber.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XslNumber.cs index cc897eaa574937..9a269e1e756420 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XslNumber.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XslNumber.cs @@ -333,36 +333,30 @@ private static string ConvertToDecimal(double val, int minLen, char zero, string } // Add both grouping separators and zero padding to the string representation of a number - unsafe + char separator = groupSeparator.Length > 0 ? groupSeparator[0] : ' '; + return string.Create(newLen, (str, shift, zero, separator, groupSize), static (result, state) => { - char* result = stackalloc char[newLen]; - char separator = (groupSeparator.Length > 0) ? groupSeparator[0] : ' '; + var (str, shift, zero, separator, groupSize) = state; + int cnt = groupSize; + int oldPos = str.Length - 1; - fixed (char* pin = str) + for (int newPos = result.Length - 1; newPos >= 0; newPos--) { - char* pOldEnd = pin + oldLen - 1; - char* pNewEnd = result + newLen - 1; - int cnt = groupSize; - - while (true) + if (groupSize != 0 && cnt == 0) + { + // Every groupSize digits insert the separator + result[newPos] = separator; + cnt = groupSize; + Debug.Assert(newPos > 0, "Separator cannot be the first character"); + } + else { // Move digit to its new location (zero if we've run out of digits) - *pNewEnd-- = (pOldEnd >= pin) ? (char)(*pOldEnd-- + shift) : zero; - if (pNewEnd < result) - { - break; - } - if (/*groupSize > 0 && */--cnt == 0) - { - // Every groupSize digits insert the separator - *pNewEnd-- = separator; - cnt = groupSize; - Debug.Assert(pNewEnd >= result, "Separator cannot be the first character"); - } + result[newPos] = oldPos >= 0 ? (char)(str[oldPos--] + shift) : zero; + cnt--; } } - return new string(result, 0, newLen); - } + }); } } } diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompilerTests.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompilerTests.cs index 0c6cfcd7a6e5c3..96bd9976e40367 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompilerTests.cs +++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompilerTests.cs @@ -111,6 +111,52 @@ public void FormatTimeWithEmptyFormatString() } } + public static TheoryData XslNumberDecimalData => new TheoryData + { + // (value, format, grouping-separator + grouping-size attributes, expectedOutput) + // Basic decimal (fast path: no changes needed) + { "1", "1", "", "1" }, + { "1234", "1", "", "1234" }, + // Padding only (fast path: PadLeft) + { "42", "001", "", "042" }, + { "5", "0001", "", "0005" }, + // Grouping separator (string.Create path) + { "1234567", "1", @"grouping-separator="","" grouping-size=""3""", "1,234,567" }, + { "1000", "1", @"grouping-separator="","" grouping-size=""3""", "1,000" }, + { "100", "1", @"grouping-separator="","" grouping-size=""3""", "100" }, + { "1234", "1", @"grouping-separator="","" grouping-size=""3""", "1,234" }, + // Padding with grouping separator (string.Create path) + { "42", "001", @"grouping-separator="","" grouping-size=""3""", "042" }, + { "42", "00001", @"grouping-separator="","" grouping-size=""3""", "00,042" }, + // Non-ASCII digit system - Arabic-Indic digits (string.Create path, shift != 0) + { "123", "\u0661", "", "\u0661\u0662\u0663" }, + { "1234", "\u0661", "", "\u0661\u0662\u0663\u0664" }, + }; + + [Theory] + [MemberData(nameof(XslNumberDecimalData))] + public void XslNumberDecimalFormatting(string value, string format, string groupingAttrs, string expected) + { + string xml = @""; + string xsl = $@" + + + + +"; + using var outWriter = new StringWriter(); + using (var xslStringReader = new StringReader(xsl)) + using (var xmlStringReader = new StringReader(xml)) + using (var xslReader = XmlReader.Create(xslStringReader)) + using (var xmlReader = XmlReader.Create(xmlStringReader)) + { + var transform = new XslCompiledTransform(); + transform.Load(xslReader); + transform.Transform(xmlReader, null, outWriter); + } + + Assert.Equal(expected, outWriter.ToString()); + } } }