From ab48c7b25fe8cbc47921bf29d0b087003e9bec95 Mon Sep 17 00:00:00 2001 From: Maksim Golev Date: Thu, 27 Mar 2025 12:58:27 +0400 Subject: [PATCH 01/10] fix(#93189): Fix invalid test. --- .../TestData/XsltApiV2/baseline/bug93189.xml | 30 ++-- .../XslCompiledTransform.cs | 139 +++++++++--------- 2 files changed, 87 insertions(+), 82 deletions(-) diff --git a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/baseline/bug93189.xml b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/baseline/bug93189.xml index a80834937aea0e..253e0b11751716 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/baseline/bug93189.xml +++ b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/baseline/bug93189.xml @@ -1,19 +1,19 @@ - - Monday, June 15, 2009 - 6/15/2009 - 6/15/2009 1:45:30 PM - 6/15/2009 - lundi 15 juin 2009 - 15/06/2009 - + + Monday, 15 June 2009 + 06/15/2009 + 06/15/2009 13:45:30 + 6/15/2009 + lundi 15 juin 2009 + 15/06/2009 + - 1:45:30 PM - 1:45 PM - 6/15/2009 1:45:30 PM - 1:45 PM - 13:45:30 - 13:45 - + 13:45:30 + 13:45 + 06/15/2009 13:45:30 + 1:45 PM + 13:45:30 + 13:45 + diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs index 00511a00ec6f19..2b3f8e67794d40 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs +++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs @@ -4,10 +4,12 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Runtime.Loader; +using System.Tests; using System.Text; using System.Xml.Tests; using System.Xml.XPath; @@ -3170,84 +3172,87 @@ public void ValidCases_ExternalURI(object param0, object param1, object param2, [Theory] public void ValidCases(object param0, object param1, object param2, object param3, object param4, object param5) { - string xslFile = FullFilePath(param0 as string); - string xmlFile = FullFilePath(param1 as string); - string baseLineFile = Path.Combine("baseline", param2 as string); - bool expectedResult = (bool)param4; - bool actualResult = false; + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + { + string xslFile = FullFilePath(param0 as string); + string xmlFile = FullFilePath(param1 as string); + string baseLineFile = Path.Combine("baseline", param2 as string); + bool expectedResult = (bool)param4; + bool actualResult = false; - XmlReader xmlReader = XmlReader.Create(xmlFile); - //Let's select randomly how to create navigator - IXPathNavigable navigator = null; - Random randGenerator = new Random(unchecked((int)DateTime.Now.Ticks)); - switch (randGenerator.Next(2)) - { - case 0: - _output.WriteLine("Using XmlDocument.CreateNavigator()"); - XmlDocument xmlDoc = new XmlDocument(); - xmlDoc.Load(xmlFile); - navigator = xmlDoc.CreateNavigator(); - break; - - case 1: - _output.WriteLine("Using XPathDocument.CreateNavigator()"); - XPathDocument xpathDoc; - using (XmlReader reader = XmlReader.Create(xmlFile)) - { - xpathDoc = new XPathDocument(reader); - navigator = xpathDoc.CreateNavigator(); - } - break; + XmlReader xmlReader = XmlReader.Create(xmlFile); + //Let's select randomly how to create navigator + IXPathNavigable navigator = null; + Random randGenerator = new Random(unchecked((int)DateTime.Now.Ticks)); + switch (randGenerator.Next(2)) + { + case 0: + _output.WriteLine("Using XmlDocument.CreateNavigator()"); + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.Load(xmlFile); + navigator = xmlDoc.CreateNavigator(); + break; - default: - break; - } + case 1: + _output.WriteLine("Using XPathDocument.CreateNavigator()"); + XPathDocument xpathDoc; + using (XmlReader reader = XmlReader.Create(xmlFile)) + { + xpathDoc = new XPathDocument(reader); + navigator = xpathDoc.CreateNavigator(); + } + break; - XmlResolver resolver = null; - switch (param3 as string) - { - case "NullResolver": - break; + default: + break; + } - case "XmlUrlResolver": - resolver = new XmlUrlResolver(); - break; + XmlResolver resolver = null; + switch (param3 as string) + { + case "NullResolver": + break; - case "CustomXmlResolver": - resolver = new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), @"XsltApiV2"))); - break; + case "XmlUrlResolver": + resolver = new XmlUrlResolver(); + break; - default: - break; - } + case "CustomXmlResolver": + resolver = new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), @"XsltApiV2"))); + break; - try - { - XslCompiledTransform localXslt = new XslCompiledTransform(); - XsltSettings settings = new XsltSettings(true, true); - using (XmlReader xslReader = XmlReader.Create(xslFile)) - localXslt.Load(xslReader, settings, resolver); + default: + break; + } - using (XmlWriter writer = XmlWriter.Create("outputFile.txt")) + try { - if (param5 as string == "XmlReader") - localXslt.Transform(xmlReader, null, writer, resolver); - else - localXslt.Transform(navigator, null, writer, resolver); - } - VerifyResult(baseLineFile, "outputFile.txt"); - actualResult = true; - } - catch (Exception ex) - { - _output.WriteLine(ex.Message); - actualResult = false; - } + XslCompiledTransform localXslt = new XslCompiledTransform(); + XsltSettings settings = new XsltSettings(true, true); + using (XmlReader xslReader = XmlReader.Create(xslFile)) + localXslt.Load(xslReader, settings, resolver); - if (actualResult != expectedResult) - Assert.Fail(); + using (XmlWriter writer = XmlWriter.Create("outputFile.txt")) + { + if (param5 as string == "XmlReader") + localXslt.Transform(xmlReader, null, writer, resolver); + else + localXslt.Transform(navigator, null, writer, resolver); + } + VerifyResult(baseLineFile, "outputFile.txt"); + actualResult = true; + } + catch (Exception ex) + { + _output.WriteLine(ex.Message); + actualResult = false; + } - return; + if (actualResult != expectedResult) + { + Assert.Fail(); + } + } } //[Variation("Invalid Arguments: null, valid, valid, valid", Pri = 0, Params = new object[] { 1, false })] From 4dc4d135f0933d791aba295cf1dffcdbe314b1e4 Mon Sep 17 00:00:00 2001 From: Maksim Golev Date: Thu, 27 Mar 2025 14:50:22 +0400 Subject: [PATCH 02/10] refactor(#93189): Changing tabs to spaces. --- .../TestFiles/TestData/XsltApiV2/bug93189.xml | 2 +- .../TestFiles/TestData/XsltApiV2/bug93189.xsl | 90 +++++++++---------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml index 19f20d615e9684..297ca4493146f9 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml +++ b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml @@ -1,3 +1,3 @@ - 2009-06-15T13:45:30 + 2009-06-15T13:45:30 diff --git a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl index 88932a64469ab6..2fe4b7bbaf7f91 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl +++ b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl @@ -1,48 +1,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 27a44ee256cd1920b0af89990bd524b87bbfc334 Mon Sep 17 00:00:00 2001 From: Maksim Golev Date: Thu, 27 Mar 2025 15:10:08 +0400 Subject: [PATCH 03/10] feat(#93189): Adding test case for check working with fractional. --- .../TestFiles/TestData/XsltApiV2/baseline/bug93189.xml | 7 ++++--- .../tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml | 2 +- .../tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl | 9 ++++++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/baseline/bug93189.xml b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/baseline/bug93189.xml index 253e0b11751716..156b0a480b96f4 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/baseline/bug93189.xml +++ b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/baseline/bug93189.xml @@ -12,8 +12,9 @@ 13:45:30 13:45 06/15/2009 13:45:30 - 1:45 PM - 13:45:30 - 13:45 + 01:45:30.876PM + 1:45 PM + 13:45:30 + 13:45 diff --git a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml index 297ca4493146f9..cbbd67b4b81021 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml +++ b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml @@ -1,3 +1,3 @@ - 2009-06-15T13:45:30 + 2009-06-15T13:45:30.87654321 diff --git a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl index 2fe4b7bbaf7f91..0b39386a32ef94 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl +++ b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl @@ -34,14 +34,17 @@ - + - + - + + + + From 3de59e5384b905e899d9e74d51dc11565bd98c40 Mon Sep 17 00:00:00 2001 From: Maksim Golev Date: Fri, 28 Mar 2025 08:19:48 +0400 Subject: [PATCH 04/10] refactor(#93189): Replacing some "InlineDataAttribute" by "MemberDataAttribute". --- .../XslCompiledTransform.cs | 199 ++++++++++++++---- 1 file changed, 161 insertions(+), 38 deletions(-) diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs index 2b3f8e67794d40..854d23b5c9773b 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs +++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs @@ -3108,6 +3108,156 @@ public void TransformStrStrResolver3(object param, XslInputType xslInputType, Re [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] public class CTransformConstructorWithFourParametersTest : XsltApiTestCaseBase2 { + public static IEnumerable ValidCasesTestData + { + get + { + yield return new object[] { + "xmlResolver_document_function.xsl", + "fruits.xml", + "xmlResolver_document_function.txt", + "CustomXmlResolver", + true, + "XmlReader" + }; + yield return new object[] { + "xmlResolver_document_function.xsl", + "fruits.xml", + "xmlResolver_document_function.txt", + "CustomXmlResolver", + true, + "IXPathNavigable" + }; + yield return new object[] { + "xmlResolver_document_function.xsl", + "fruits.xml", + "xmlResolver_document_function.txt", + "XmlUrlResolver", + true, + "IXPathNavigable" + }; + yield return new object[] { + "xmlResolver_document_function.xsl", + "fruits.xml", + "xmlResolver_document_function.txt", + "XmlUrlResolver", + true, + "XmlReader" + }; + yield return new object[] { + "Bug382198.xsl", + "fruits.xml", + "bug382198.txt", + "CustomXmlResolver", + true, + "IXPathNavigable" + }; + yield return new object[] { + "Bug382198.xsl", + "fruits.xml", + "bug382198.txt", + "CustomXmlResolver", + true, + "XmlReader" + }; + yield return new object[] { + "XmlResolver_Main.xsl", + "fruits.xml", + "xmlResolver_main.txt", + "XmlUrlResolver", + true, + "IXPathNavigable" + }; + yield return new object[] { + "XmlResolver_Main.xsl", + "fruits.xml", + "xmlResolver_main.txt", + "XmlUrlResolver", + true, + "XmlReader" + }; + yield return new object[] { + "Bug382198.xsl", + "fruits.xml", + "bug382198.txt", + "XmlUrlResolver", + true, + "IXPathNavigable" + }; + yield return new object[] { + "Bug382198.xsl", + "fruits.xml", + "bug382198.txt", + "XmlUrlResolver", + true, + "XmlReader" + }; + yield return new object[] { + "Bug382198.xsl", + "fruits.xml", + "bug382198.txt", + "NullResolver", + true, + "IXPathNavigable" + }; + yield return new object[] { + "Bug382198.xsl", + "fruits.xml", + "bug382198.txt", + "NullResolver", + true, + "XmlReader" + }; + yield return new object[] { + "bug93189.xsl", + "bug93189.xml", + "bug93189.xml", + "NullResolver", + true, + "XmlReader" + }; + } + } + + public static IEnumerable ValidCasesExternalUriTestData + { + get + { + yield return new object[] { + "XmlResolver_Main.xsl", + "fruits.xml", + "xmlResolver_main.txt", + "CustomXmlResolver", + true, + "IXPathNavigable" + }; + yield return new object[] { + "XmlResolver_Main.xsl", + "fruits.xml", + "xmlResolver_main.txt", + "CustomXmlResolver", + true, + "XmlReader" + }; + yield return new object[] { + "XmlResolver_Main.xsl", + "fruits.xml", + "xmlResolver_main.txt", + "NullResolver", + false, + "IXPathNavigable" + }; + yield return new object[] { + "XmlResolver_Main.xsl", + "fruits.xml", + "xmlResolver_main.txt", + "NullResolver", + false, + "XmlReader" + }; + } + } + private ITestOutputHelper _output; public CTransformConstructorWithFourParametersTest(ITestOutputHelper output) : base(output) { @@ -3131,14 +3281,9 @@ public override Uri ResolveUri(Uri baseUri, string relativeUri) } } - //[Variation("Import/Include, CustomXmlResolver", Pri = 0, Params = new object[] { "XmlResolver_Main.xsl", "fruits.xml", "xmlResolver_main.txt", "CustomXmlResolver", true })] - [InlineData("XmlResolver_Main.xsl", "fruits.xml", "xmlResolver_main.txt", "CustomXmlResolver", true, "IXPathNavigable")] - [InlineData("XmlResolver_Main.xsl", "fruits.xml", "xmlResolver_main.txt", "CustomXmlResolver", true, "XmlReader")] - //[Variation("Import/Include, NullResolver", Pri = 0, Params = new object[] { "XmlResolver_Main.xsl", "fruits.xml", "xmlResolver_main.txt", "NullResolver", false })] - [InlineData("XmlResolver_Main.xsl", "fruits.xml", "xmlResolver_main.txt", "NullResolver", false, "IXPathNavigable")] - [InlineData("XmlResolver_Main.xsl", "fruits.xml", "xmlResolver_main.txt", "NullResolver", false, "XmlReader")] [Theory] - public void ValidCases_ExternalURI(object param0, object param1, object param2, object param3, object param4, object param5) + [MemberData(nameof(ValidCasesExternalUriTestData))] + public void ValidCases_ExternalUri(string param0, string param1, string param2, string param3, bool param4, string param5) { using (new AllowDefaultResolverContext()) { @@ -3146,38 +3291,16 @@ public void ValidCases_ExternalURI(object param0, object param1, object param2, } } - //[Variation("Document function 1, CustomXmlResolver", Pri = 0, Params = new object[] { "xmlResolver_document_function.xsl", "fruits.xml", "xmlResolver_document_function.txt", "CustomXmlResolver", true })] - [InlineData("xmlResolver_document_function.xsl", "fruits.xml", "xmlResolver_document_function.txt", "CustomXmlResolver", true, "XmlReader")] - [InlineData("xmlResolver_document_function.xsl", "fruits.xml", "xmlResolver_document_function.txt", "CustomXmlResolver", true, "IXPathNavigable")] - //[Variation("Document function 1, XmlUrlResolver", Pri = 0, Params = new object[] { "xmlResolver_document_function.xsl", "fruits.xml", "xmlResolver_document_function.txt", "XmlUrlResolver", true })] - [InlineData("xmlResolver_document_function.xsl", "fruits.xml", "xmlResolver_document_function.txt", "XmlUrlResolver", true, "IXPathNavigable")] - [InlineData("xmlResolver_document_function.xsl", "fruits.xml", "xmlResolver_document_function.txt", "XmlUrlResolver", true, "XmlReader")] - //[Variation("Document function 1, NullResolver", Pri = 0, Params = new object[] { "xmlResolver_document_function.xsl", "fruits.xml", "xmlResolver_document_function.txt", "NullResolver", false })] - // [InlineData("xmlResolver_document_function.xsl", "fruits.xml", "xmlResolver_document_function.txt", "NullResolver", false, "IXPathNavigable")] - // [InlineData("xmlResolver_document_function.xsl", "fruits.xml", "xmlResolver_document_function.txt", "NullResolver", false, "XmlReader")] - //[Variation("No Import/Include, CustomXmlResolver", Pri = 0, Params = new object[] { "Bug382198.xsl", "fruits.xml", "bug382198.txt", "CustomXmlResolver", true })] - [InlineData("Bug382198.xsl", "fruits.xml", "bug382198.txt", "CustomXmlResolver", true, "IXPathNavigable")] - [InlineData("Bug382198.xsl", "fruits.xml", "bug382198.txt", "CustomXmlResolver", true, "XmlReader")] - //[Variation("Import/Include, XmlUrlResolver", Pri = 0, Params = new object[] { "XmlResolver_Main.xsl", "fruits.xml", "xmlResolver_main.txt", "XmlUrlResolver", true })] - [InlineData("XmlResolver_Main.xsl", "fruits.xml", "xmlResolver_main.txt", "XmlUrlResolver", true, "IXPathNavigable")] - [InlineData("XmlResolver_Main.xsl", "fruits.xml", "xmlResolver_main.txt", "XmlUrlResolver", true, "XmlReader")] - //[Variation("No Import/Include, XmlUrlResolver", Pri = 0, Params = new object[] { "Bug382198.xsl", "fruits.xml", "bug382198.txt", "XmlUrlResolver", true })] - [InlineData("Bug382198.xsl", "fruits.xml", "bug382198.txt", "XmlUrlResolver", true, "IXPathNavigable")] - [InlineData("Bug382198.xsl", "fruits.xml", "bug382198.txt", "XmlUrlResolver", true, "XmlReader")] - //[Variation("No Import/Include, NullResolver", Pri = 0, Params = new object[] { "Bug382198.xsl", "fruits.xml", "bug382198.txt", "NullResolver", true })] - [InlineData("Bug382198.xsl", "fruits.xml", "bug382198.txt", "NullResolver", true, "IXPathNavigable")] - [InlineData("Bug382198.xsl", "fruits.xml", "bug382198.txt", "NullResolver", true, "XmlReader")] - // [ActiveIssue(https://github.com/dotnet/runtime/issues/115455)] - // [InlineData("bug93189.xsl", "bug93189.xml", "bug93189.xml", "NullResolver", true, "XmlReader")] - [Theory] - public void ValidCases(object param0, object param1, object param2, object param3, object param4, object param5) + [Theory] + [MemberData(nameof(ValidCasesTestData))] + public void ValidCases(string param0, string param1, string param2, string param3, bool param4, string param5) { using (new ThreadCultureChange(CultureInfo.InvariantCulture)) { - string xslFile = FullFilePath(param0 as string); - string xmlFile = FullFilePath(param1 as string); - string baseLineFile = Path.Combine("baseline", param2 as string); - bool expectedResult = (bool)param4; + string xslFile = FullFilePath(param0); + string xmlFile = FullFilePath(param1); + string baseLineFile = Path.Combine("baseline", param2); + bool expectedResult = param4; bool actualResult = false; XmlReader xmlReader = XmlReader.Create(xmlFile); @@ -3208,7 +3331,7 @@ public void ValidCases(object param0, object param1, object param2, object param } XmlResolver resolver = null; - switch (param3 as string) + switch (param3) { case "NullResolver": break; @@ -3234,7 +3357,7 @@ public void ValidCases(object param0, object param1, object param2, object param using (XmlWriter writer = XmlWriter.Create("outputFile.txt")) { - if (param5 as string == "XmlReader") + if (param5 == "XmlReader") localXslt.Transform(xmlReader, null, writer, resolver); else localXslt.Transform(navigator, null, writer, resolver); From 84b163f450b3395f6c7a6180b9ccf9c9fd45f3f6 Mon Sep 17 00:00:00 2001 From: Maksim Golev Date: Fri, 28 Mar 2025 09:17:41 +0400 Subject: [PATCH 05/10] refactor(#93189): Refactoring some tests. --- .../Xslt/XslCompiledTransformApi/Errata4.cs | 5 - .../XslCompiledTransform.cs | 429 ++++++++++-------- .../XslTransformMultith.cs | 5 +- .../Xslt/XslCompiledTransformApi/XsltApiV2.cs | 33 +- .../XsltArgumentListMultith.cs | 6 +- 5 files changed, 272 insertions(+), 206 deletions(-) diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/Errata4.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/Errata4.cs index 0b5c13f8d95c0a..d065091d04685f 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/Errata4.cs +++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/Errata4.cs @@ -373,10 +373,5 @@ private string GenerateString(char c, CharType charType) throw new CTestFailedException("TEST ISSUE: CharType FAILURE!"); } } - - public new int Init(object objParam) - { - return 1; - } } } diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs index 854d23b5c9773b..d586c2dab8c5b6 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs +++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs @@ -3108,152 +3108,248 @@ public void TransformStrStrResolver3(object param, XslInputType xslInputType, Re [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] public class CTransformConstructorWithFourParametersTest : XsltApiTestCaseBase2 { - public static IEnumerable ValidCasesTestData + public static IEnumerable LoadXmlFileExternalUriShouldThrowXsltExceptionTestData { get { yield return new object[] { - "xmlResolver_document_function.xsl", - "fruits.xml", - "xmlResolver_document_function.txt", - "CustomXmlResolver", - true, - "XmlReader" + FullFilePath("XmlResolver_Main.xsl"), + null }; + } + } + + public static IEnumerable TransformWithXmlReaderXmlFileReturnsExpectedBaseLineFileCasesTestData + { + get + { + yield return new object[] { + FullFilePath("xmlResolver_document_function.xsl"), + FullFilePath("fruits.xml"), + Path.Combine("baseline", "xmlResolver_document_function.txt"), + new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), "XsltApiV2"))) + }; + yield return new object[] { + FullFilePath("xmlResolver_document_function.xsl"), + FullFilePath("fruits.xml"), + Path.Combine("baseline", "xmlResolver_document_function.txt"), + new XmlUrlResolver() + }; + yield return new object[] { + FullFilePath("Bug382198.xsl"), + FullFilePath("fruits.xml"), + Path.Combine("baseline", "bug382198.txt"), + new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), "XsltApiV2"))) + }; + yield return new object[] { + FullFilePath("XmlResolver_Main.xsl"), + FullFilePath("fruits.xml"), + Path.Combine("baseline", "xmlResolver_main.txt"), + new XmlUrlResolver() + }; + yield return new object[] { + FullFilePath("Bug382198.xsl"), + FullFilePath("fruits.xml"), + Path.Combine("baseline", "bug382198.txt"), + new XmlUrlResolver() + }; + yield return new object[] { + FullFilePath("Bug382198.xsl"), + FullFilePath("fruits.xml"), + Path.Combine("baseline", "bug382198.txt"), + null + }; + yield return new object[] { + FullFilePath("bug93189.xsl"), + FullFilePath("bug93189.xml"), + Path.Combine("baseline", "bug93189.xml"), + null + }; + } + } + + public static IEnumerable TransformWithIXPathNavigableXmlFileReturnsExpectedBaseLineFileCasesTestData + { + get + { + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.Load(FullFilePath("fruits.xml")); yield return new object[] { - "xmlResolver_document_function.xsl", - "fruits.xml", - "xmlResolver_document_function.txt", - "CustomXmlResolver", - true, - "IXPathNavigable" + FullFilePath("xmlResolver_document_function.xsl"), + xmlDoc.CreateNavigator(), + Path.Combine("baseline", "xmlResolver_document_function.txt"), + new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), "XsltApiV2"))) }; + + XPathDocument xpathDoc; + IXPathNavigable navigator; + using (XmlReader reader = XmlReader.Create(FullFilePath("fruits.xml"))) + { + xpathDoc = new XPathDocument(reader); + navigator = xpathDoc.CreateNavigator(); + } yield return new object[] { - "xmlResolver_document_function.xsl", - "fruits.xml", - "xmlResolver_document_function.txt", - "XmlUrlResolver", - true, - "IXPathNavigable" + FullFilePath("xmlResolver_document_function.xsl"), + navigator, + Path.Combine("baseline", "xmlResolver_document_function.txt"), + new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), "XsltApiV2"))) }; + + XmlDocument xmlDoc2 = new XmlDocument(); + xmlDoc2.Load(FullFilePath("fruits.xml")); yield return new object[] { - "xmlResolver_document_function.xsl", - "fruits.xml", - "xmlResolver_document_function.txt", - "XmlUrlResolver", - true, - "XmlReader" + FullFilePath("xmlResolver_document_function.xsl"), + xmlDoc2.CreateNavigator(), + Path.Combine("baseline", "xmlResolver_document_function.txt"), + new XmlUrlResolver() }; + + XPathDocument xpathDoc2; + IXPathNavigable navigator2; + using (XmlReader reader = XmlReader.Create(FullFilePath("fruits.xml"))) + { + xpathDoc2 = new XPathDocument(reader); + navigator2 = xpathDoc.CreateNavigator(); + } yield return new object[] { - "Bug382198.xsl", - "fruits.xml", - "bug382198.txt", - "CustomXmlResolver", - true, - "IXPathNavigable" + FullFilePath("xmlResolver_document_function.xsl"), + navigator2, + Path.Combine("baseline", "xmlResolver_document_function.txt"), + new XmlUrlResolver() }; + + XmlDocument xmlDoc3 = new XmlDocument(); + xmlDoc3.Load(FullFilePath("fruits.xml")); yield return new object[] { - "Bug382198.xsl", - "fruits.xml", - "bug382198.txt", - "CustomXmlResolver", - true, - "XmlReader" + FullFilePath("Bug382198.xsl"), + xmlDoc3.CreateNavigator(), + Path.Combine("baseline", "bug382198.txt"), + new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), "XsltApiV2"))) }; + + XPathDocument xpathDoc3; + IXPathNavigable navigator3; + using (XmlReader reader = XmlReader.Create(FullFilePath("fruits.xml"))) + { + xpathDoc3 = new XPathDocument(reader); + navigator3 = xpathDoc.CreateNavigator(); + } yield return new object[] { - "XmlResolver_Main.xsl", - "fruits.xml", - "xmlResolver_main.txt", - "XmlUrlResolver", - true, - "IXPathNavigable" + FullFilePath("Bug382198.xsl"), + navigator3, + Path.Combine("baseline", "bug382198.txt"), + new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), "XsltApiV2"))) }; + + XmlDocument xmlDoc4 = new XmlDocument(); + xmlDoc4.Load(FullFilePath("fruits.xml")); yield return new object[] { - "XmlResolver_Main.xsl", - "fruits.xml", - "xmlResolver_main.txt", - "XmlUrlResolver", - true, - "XmlReader" + FullFilePath("XmlResolver_Main.xsl"), + xmlDoc4.CreateNavigator(), + Path.Combine("baseline", "xmlResolver_main.txt"), + new XmlUrlResolver() }; + + XPathDocument xpathDoc4; + IXPathNavigable navigator4; + using (XmlReader reader = XmlReader.Create(FullFilePath("fruits.xml"))) + { + xpathDoc4 = new XPathDocument(reader); + navigator4 = xpathDoc.CreateNavigator(); + } yield return new object[] { - "Bug382198.xsl", - "fruits.xml", - "bug382198.txt", - "XmlUrlResolver", - true, - "IXPathNavigable" + FullFilePath("XmlResolver_Main.xsl"), + navigator4, + Path.Combine("baseline", "xmlResolver_main.txt"), + new XmlUrlResolver() }; + + XmlDocument xmlDoc5 = new XmlDocument(); + xmlDoc5.Load(FullFilePath("fruits.xml")); yield return new object[] { - "Bug382198.xsl", - "fruits.xml", - "bug382198.txt", - "XmlUrlResolver", - true, - "XmlReader" + FullFilePath("Bug382198.xsl"), + xmlDoc5.CreateNavigator(), + Path.Combine("baseline", "bug382198.txt"), + new XmlUrlResolver() }; + + XPathDocument xpathDoc5; + IXPathNavigable navigator5; + using (XmlReader reader = XmlReader.Create(FullFilePath("fruits.xml"))) + { + xpathDoc5 = new XPathDocument(reader); + navigator5 = xpathDoc.CreateNavigator(); + } yield return new object[] { - "Bug382198.xsl", - "fruits.xml", - "bug382198.txt", - "NullResolver", - true, - "IXPathNavigable" + FullFilePath("Bug382198.xsl"), + navigator5, + Path.Combine("baseline", "bug382198.txt"), + new XmlUrlResolver() }; + + XmlDocument xmlDoc6 = new XmlDocument(); + xmlDoc6.Load(FullFilePath("fruits.xml")); yield return new object[] { - "Bug382198.xsl", - "fruits.xml", - "bug382198.txt", - "NullResolver", - true, - "XmlReader" + FullFilePath("Bug382198.xsl"), + xmlDoc6.CreateNavigator(), + Path.Combine("baseline", "bug382198.txt"), + null }; + + XPathDocument xpathDoc6; + IXPathNavigable navigator6; + using (XmlReader reader = XmlReader.Create(FullFilePath("fruits.xml"))) + { + xpathDoc6 = new XPathDocument(reader); + navigator6 = xpathDoc.CreateNavigator(); + } yield return new object[] { - "bug93189.xsl", - "bug93189.xml", - "bug93189.xml", - "NullResolver", - true, - "XmlReader" + FullFilePath("Bug382198.xsl"), + navigator6, + Path.Combine("baseline", "bug382198.txt"), + null }; } } - public static IEnumerable ValidCasesExternalUriTestData + public static IEnumerable TransformWithIXPathNavigableXmlFileExternalUriReturnsExpectedBaseLineFileTestData { get { + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.Load(FullFilePath("fruits.xml")); yield return new object[] { - "XmlResolver_Main.xsl", - "fruits.xml", - "xmlResolver_main.txt", - "CustomXmlResolver", - true, - "IXPathNavigable" - }; - yield return new object[] { - "XmlResolver_Main.xsl", - "fruits.xml", - "xmlResolver_main.txt", - "CustomXmlResolver", - true, - "XmlReader" + FullFilePath("XmlResolver_Main.xsl"), + xmlDoc.CreateNavigator(), + Path.Combine("baseline", "xmlResolver_main.txt"), + new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), "XsltApiV2"))) }; + + XPathDocument xpathDoc; + IXPathNavigable navigator; + using (XmlReader reader = XmlReader.Create(FullFilePath("fruits.xml"))) + { + xpathDoc = new XPathDocument(reader); + navigator = xpathDoc.CreateNavigator(); + } yield return new object[] { - "XmlResolver_Main.xsl", - "fruits.xml", - "xmlResolver_main.txt", - "NullResolver", - false, - "IXPathNavigable" + FullFilePath("XmlResolver_Main.xsl"), + navigator, + Path.Combine("baseline", "xmlResolver_main.txt"), + new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), "XsltApiV2"))) }; + } + } + + public static IEnumerable TransformWithXmlReaderXmlFileExternalUriReturnsExpectedBaseLineFileTestData + { + get + { yield return new object[] { - "XmlResolver_Main.xsl", - "fruits.xml", - "xmlResolver_main.txt", - "NullResolver", - false, - "XmlReader" + FullFilePath("XmlResolver_Main.xsl"), + FullFilePath("fruits.xml"), + Path.Combine("baseline", "xmlResolver_main.txt"), + new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), "XsltApiV2"))) }; } } @@ -3282,99 +3378,78 @@ public override Uri ResolveUri(Uri baseUri, string relativeUri) } [Theory] - [MemberData(nameof(ValidCasesExternalUriTestData))] - public void ValidCases_ExternalUri(string param0, string param1, string param2, string param3, bool param4, string param5) + [MemberData(nameof(TransformWithIXPathNavigableXmlFileExternalUriReturnsExpectedBaseLineFileTestData))] + public void TransformWithIXPathNavigable_XmlFileExternalUri_ReturnsExpectedBaseLineFile(string xslFile, IXPathNavigable navigator, string baseLineFile, XmlResolver resolver) { using (new AllowDefaultResolverContext()) { - ValidCases(param0, param1, param2, param3, param4, param5); + TransformWithIXPathNavigable_XmlFile_ReturnsExpectedBaseLineFile(xslFile, navigator, baseLineFile, resolver); } } [Theory] - [MemberData(nameof(ValidCasesTestData))] - public void ValidCases(string param0, string param1, string param2, string param3, bool param4, string param5) + [MemberData(nameof(TransformWithXmlReaderXmlFileExternalUriReturnsExpectedBaseLineFileTestData))] + public void TransformWithXmlReader_XmlFileExternalUri_ReturnsExpectedBaseLineFile(string xslFile, string xmlFile, string baseLineFile, XmlResolver resolver) { - using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + using (new AllowDefaultResolverContext()) { - string xslFile = FullFilePath(param0); - string xmlFile = FullFilePath(param1); - string baseLineFile = Path.Combine("baseline", param2); - bool expectedResult = param4; - bool actualResult = false; - - XmlReader xmlReader = XmlReader.Create(xmlFile); - //Let's select randomly how to create navigator - IXPathNavigable navigator = null; - Random randGenerator = new Random(unchecked((int)DateTime.Now.Ticks)); - switch (randGenerator.Next(2)) - { - case 0: - _output.WriteLine("Using XmlDocument.CreateNavigator()"); - XmlDocument xmlDoc = new XmlDocument(); - xmlDoc.Load(xmlFile); - navigator = xmlDoc.CreateNavigator(); - break; + TransformWithXmlReader_XmlFile_ReturnsExpectedBaseLineFile(xslFile, xmlFile, baseLineFile, resolver); + } + } - case 1: - _output.WriteLine("Using XPathDocument.CreateNavigator()"); - XPathDocument xpathDoc; - using (XmlReader reader = XmlReader.Create(xmlFile)) - { - xpathDoc = new XPathDocument(reader); - navigator = xpathDoc.CreateNavigator(); - } - break; + [Theory] + [MemberData(nameof(LoadXmlFileExternalUriShouldThrowXsltExceptionTestData))] + public void Load_XmlFileExternalUri_ShouldThrowXsltException(string xslFile, XmlResolver resolver) + { + XslCompiledTransform localXslt = new XslCompiledTransform(); + XsltSettings settings = new XsltSettings(true, true); + using (XmlReader xslReader = XmlReader.Create(xslFile)) + { + Assert.ThrowsAny(() => localXslt.Load(xslReader, settings, resolver)); + } + } - default: - break; + [Theory] + [MemberData(nameof(TransformWithIXPathNavigableXmlFileReturnsExpectedBaseLineFileCasesTestData))] + public void TransformWithIXPathNavigable_XmlFile_ReturnsExpectedBaseLineFile(string xslFile, IXPathNavigable navigator, string baseLineFile, XmlResolver resolver) + { + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + { + XslCompiledTransform localXslt = new XslCompiledTransform(); + XsltSettings settings = new XsltSettings(true, true); + using (XmlReader xslReader = XmlReader.Create(xslFile)) + { + localXslt.Load(xslReader, settings, resolver); } - XmlResolver resolver = null; - switch (param3) + using (XmlWriter writer = XmlWriter.Create("outputFile.txt")) { - case "NullResolver": - break; - - case "XmlUrlResolver": - resolver = new XmlUrlResolver(); - break; - - case "CustomXmlResolver": - resolver = new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), @"XsltApiV2"))); - break; - - default: - break; + localXslt.Transform(navigator, null, writer, resolver); } + VerifyResult(baseLineFile, "outputFile.txt"); + } + } - try - { - XslCompiledTransform localXslt = new XslCompiledTransform(); - XsltSettings settings = new XsltSettings(true, true); - using (XmlReader xslReader = XmlReader.Create(xslFile)) - localXslt.Load(xslReader, settings, resolver); + [Theory] + [MemberData(nameof(TransformWithXmlReaderXmlFileReturnsExpectedBaseLineFileCasesTestData))] + public void TransformWithXmlReader_XmlFile_ReturnsExpectedBaseLineFile(string xslFile, string xmlFile, string baseLineFile, XmlResolver resolver) + { + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + { + XmlReader xmlReader = XmlReader.Create(xmlFile); - using (XmlWriter writer = XmlWriter.Create("outputFile.txt")) - { - if (param5 == "XmlReader") - localXslt.Transform(xmlReader, null, writer, resolver); - else - localXslt.Transform(navigator, null, writer, resolver); - } - VerifyResult(baseLineFile, "outputFile.txt"); - actualResult = true; - } - catch (Exception ex) + XslCompiledTransform localXslt = new XslCompiledTransform(); + XsltSettings settings = new XsltSettings(true, true); + using (XmlReader xslReader = XmlReader.Create(xslFile)) { - _output.WriteLine(ex.Message); - actualResult = false; + localXslt.Load(xslReader, settings, resolver); } - if (actualResult != expectedResult) + using (XmlWriter writer = XmlWriter.Create("outputFile.txt")) { - Assert.Fail(); + localXslt.Transform(xmlReader, null, writer, resolver); } + VerifyResult(baseLineFile, "outputFile.txt"); } } diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslTransformMultith.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslTransformMultith.cs index 5699868c280638..1e075e70588eae 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslTransformMultith.cs +++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslTransformMultith.cs @@ -23,14 +23,13 @@ public class SameInstanceXslTransformTestCase : XsltApiTestCaseBase2 public SameInstanceXslTransformTestCase(ITestOutputHelper output) : base(output) { _output = output; - Init(null); + Init(); } - public new void Init(object objParam) + public void Init() { xsltSameInstance = new XslCompiledTransform(); _strPath = Path.Combine("TestFiles", FilePathUtil.GetTestDataPath(), "XsltApiV2"); - return; } } diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltApiV2.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltApiV2.cs index a4cee46f961fe2..98a905c64479c0 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltApiV2.cs +++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltApiV2.cs @@ -51,10 +51,16 @@ public class XsltApiTestCaseBase2 public string[] szWhiteSpace = { " ", "\n", "\t", "\r", "\t\n \r\t" }; public string szSimple = "myArg"; - // Variables from init string - private string _strPath; // Path of the data files + protected static string _standardTests; - private string _httpPath; // Http Path of the data files + /// + /// Path of the data files. + /// + private static string _strPath; + /// + /// Http Path of the data files. + /// + private static string _httpPath; // Other global variables protected string _strOutFile = "out.xml"; // File to create when using write transforms @@ -64,14 +70,12 @@ public class XsltApiTestCaseBase2 protected XsltArgumentList m_xsltArg; // For XsltArgumentList tests public object retObj; - protected string _standardTests; - private ITestOutputHelper _output; + public XsltApiTestCaseBase2(ITestOutputHelper output) { AppContext.SetSwitch("TestSwitch.LocalAppContext.DisableCaching", true); _output = output; - this.Init(null); } static XsltApiTestCaseBase2() @@ -84,6 +88,11 @@ static XsltApiTestCaseBase2() string xslString = doc.OuterXml.Replace("ABSOLUTE_URI", targetFile); doc.LoadXml(xslString); doc.Save(xslFile); + + //This is a temporary fix to restore the baselines. Refer to Test bug # + _strPath = Path.Combine("TestFiles", FilePathUtil.GetTestDataPath(), "XsltApiV2"); + _httpPath = Path.Combine(FilePathUtil.GetHttpTestDataPath(), "XsltApiV2"); + _standardTests = Path.Combine("TestFiles", FilePathUtil.GetHttpStandardPath(), "xslt10", "Current"); } public OutputType GetOutputType(string s) @@ -159,17 +168,7 @@ public ReaderType GetReaderType(string s) } } - public void Init(object objParam) - { - //This is a temporary fix to restore the baselines. Refer to Test bug # - _strPath = Path.Combine("TestFiles", FilePathUtil.GetTestDataPath(), "XsltApiV2"); - _httpPath = Path.Combine(FilePathUtil.GetHttpTestDataPath(), "XsltApiV2"); - _standardTests = Path.Combine("TestFiles", FilePathUtil.GetHttpStandardPath(), "xslt10","Current"); - - return; - } - - public string FullFilePath(string szFile) + public static string FullFilePath(string szFile) { if (szFile == null || szFile == string.Empty) return szFile; diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltArgumentListMultith.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltArgumentListMultith.cs index bfa3788d0c9072..e93afa22b14841 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltArgumentListMultith.cs +++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltArgumentListMultith.cs @@ -23,10 +23,10 @@ public class CSameInstanceXsltArgTestCase2 : XsltApiTestCaseBase2 public CSameInstanceXsltArgTestCase2(ITestOutputHelper output) : base(output) { _output = output; - Init(null); + Init(); } - public new void Init(object objParam) + public void Init() { // Get parameter info _strPath = Path.Combine("TestFiles", FilePathUtil.GetTestDataPath(), "XsltApiV2"); @@ -49,8 +49,6 @@ public CSameInstanceXsltArgTestCase2(ITestOutputHelper output) : base(output) xsltArg1.AddParam("myArg3", szEmpty, "Test3"); xsltArg1.AddParam("myArg4", szEmpty, "Test4"); xsltArg1.AddParam("myArg5", szEmpty, "Test5"); - - return; } } From 95ff95a35916f2084978468628a7cf5f2aab473f Mon Sep 17 00:00:00 2001 From: Maksim Golev Date: Mon, 31 Mar 2025 14:26:27 +0400 Subject: [PATCH 06/10] refactor(#93189): Extracting "XsdDateTimeParser" and it's environments to separate files. --- .../src/System.Private.Xml.csproj | 3 + .../DateAndTime/Parsers/XsdDateTimeParser.cs | 413 +++++++++++++++ .../Specifications/DateTimeTypeCode.cs | 21 + .../Specifications/XsdDateTimeKind.cs | 16 + .../src/System/Xml/Schema/XsdDateTime.cs | 482 +----------------- 5 files changed, 478 insertions(+), 457 deletions(-) create mode 100644 src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs create mode 100644 src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/DateTimeTypeCode.cs create mode 100644 src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/XsdDateTimeKind.cs diff --git a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj index bd3f70ad7a344a..a4cd146465a1c9 100644 --- a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj +++ b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj @@ -42,6 +42,9 @@ True TextUtf8RawTextWriter.tt + + + TextTemplatingFileGenerator HtmlEncodedRawTextWriter.cs diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs new file mode 100644 index 00000000000000..c450c45aeae0f8 --- /dev/null +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs @@ -0,0 +1,413 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Xml.Schema.DateAndTime.Specifications; + +namespace System.Xml.Schema.DateAndTime.Parsers +{ + internal struct XsdDateTimeParser + { + /// + /// Maximum number of fraction digits. + /// + public const short MaxFractionDigits = 7; + private const int firstDay = 1; + private const int firstMonth = 1; + private const int leapYear = 1904; + + public static readonly int s_Lz_ = "-".Length; + public static readonly int s_lz_zz = "-zz".Length; + public static readonly int s_lz_zz_ = "-zz:".Length; + public static readonly int s_lz_zz_zz = "-zz:zz".Length; + public static readonly int s_lzHH = "HH".Length; + public static readonly int s_lzHH_ = "HH:".Length; + public static readonly int s_lzHH_mm = "HH:mm".Length; + public static readonly int s_lzHH_mm_ = "HH:mm:".Length; + public static readonly int s_lzHH_mm_ss = "HH:mm:ss".Length; + public static readonly int s_lzyyyy = "yyyy".Length; + public static readonly int s_lzyyyy_ = "yyyy-".Length; + public static readonly int s_lzyyyy_MM = "yyyy-MM".Length; + public static readonly int s_lzyyyy_MM_ = "yyyy-MM-".Length; + public static readonly int s_lzyyyy_MM_dd = "yyyy-MM-dd".Length; + private static readonly int s_Lz__ = "--".Length; + private static readonly int s_Lz___ = "---".Length; + private static readonly int s_lz___dd = "---dd".Length; + private static readonly int s_lz__mm = "--MM".Length; + private static readonly int s_lz__mm_ = "--MM-".Length; + private static readonly int s_lz__mm__ = "--MM--".Length; + private static readonly int s_lz__mm_dd = "--MM-dd".Length; + private static readonly int s_lzyyyy_MM_ddT = "yyyy-MM-ddT".Length; + + public int day; + public int fraction; + public int hour; + public XsdDateTimeKind kind; + public int minute; + public int month; + public int second; + public DateTimeTypeCode typeCode; + public int year; + public int zoneHour; + public int zoneMinute; + + private int _length; + private string _text; + private static ReadOnlySpan Power10 => [-1, 10, 100, 1000, 10000, 100000, 1000000]; + + public bool Parse(string text, XsdDateTimeFlags kinds) + { + _text = text; + _length = text.Length; + + // Skip leading whitespace + int start = 0; + while (start < _length && char.IsWhiteSpace(text[start])) + { + start++; + } + // Choose format starting from the most common and trying not to reparse the same thing too many times + if (Test(kinds, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.Date | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrDateTimeNoTz)) + { + if (ParseDate(start)) + { + if (Test(kinds, XsdDateTimeFlags.DateTime)) + { + if (ParseChar(start + s_lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT)) + { + typeCode = DateTimeTypeCode.DateTime; + return true; + } + } + if (Test(kinds, XsdDateTimeFlags.Date)) + { + if (ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd)) + { + typeCode = DateTimeTypeCode.Date; + return true; + } + } + + if (Test(kinds, XsdDateTimeFlags.XdrDateTime)) + { + if (ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd) || (ParseChar(start + s_lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT))) + { + typeCode = DateTimeTypeCode.XdrDateTime; + return true; + } + } + if (Test(kinds, XsdDateTimeFlags.XdrDateTimeNoTz)) + { + if (ParseChar(start + s_lzyyyy_MM_dd, 'T')) + { + if (ParseTimeAndWhitespace(start + s_lzyyyy_MM_ddT)) + { + typeCode = DateTimeTypeCode.XdrDateTime; + return true; + } + } + else + { + typeCode = DateTimeTypeCode.XdrDateTime; + return true; + } + } + } + } + + if (Test(kinds, XsdDateTimeFlags.Time)) + { + if (ParseTimeAndZoneAndWhitespace(start)) + { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time + year = leapYear; + month = firstMonth; + day = firstDay; + typeCode = DateTimeTypeCode.Time; + return true; + } + } + + if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz)) + { + if (ParseTimeAndWhitespace(start)) + { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time + year = leapYear; + month = firstMonth; + day = firstDay; + typeCode = DateTimeTypeCode.Time; + return true; + } + } + + if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear)) + { + if (Parse4Dig(start, ref year) && 1 <= year) + { + if (Test(kinds, XsdDateTimeFlags.GYearMonth)) + { + if ( + ParseChar(start + s_lzyyyy, '-') && + Parse2Dig(start + s_lzyyyy_, ref month) && 1 <= month && month <= 12 && + ParseZoneAndWhitespace(start + s_lzyyyy_MM) + ) + { + day = firstDay; + typeCode = DateTimeTypeCode.GYearMonth; + return true; + } + } + if (Test(kinds, XsdDateTimeFlags.GYear)) + { + if (ParseZoneAndWhitespace(start + s_lzyyyy)) + { + month = firstMonth; + day = firstDay; + typeCode = DateTimeTypeCode.GYear; + return true; + } + } + } + } + if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth)) + { + if ( + ParseChar(start, '-') && + ParseChar(start + s_Lz_, '-') && + Parse2Dig(start + s_Lz__, ref month) && 1 <= month && month <= 12 + ) + { + if (Test(kinds, XsdDateTimeFlags.GMonthDay) && ParseChar(start + s_lz__mm, '-')) + { + if ( + Parse2Dig(start + s_lz__mm_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month) && + ParseZoneAndWhitespace(start + s_lz__mm_dd) + ) + { + year = leapYear; + typeCode = DateTimeTypeCode.GMonthDay; + return true; + } + } + if (Test(kinds, XsdDateTimeFlags.GMonth)) + { + if (ParseZoneAndWhitespace(start + s_lz__mm) || (ParseChar(start + s_lz__mm, '-') && ParseChar(start + s_lz__mm_, '-') && ParseZoneAndWhitespace(start + s_lz__mm__))) + { + year = leapYear; + day = firstDay; + typeCode = DateTimeTypeCode.GMonth; + return true; + } + } + } + } + if (Test(kinds, XsdDateTimeFlags.GDay)) + { + if ( + ParseChar(start, '-') && + ParseChar(start + s_Lz_, '-') && + ParseChar(start + s_Lz__, '-') && + Parse2Dig(start + s_Lz___, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, firstMonth) && + ParseZoneAndWhitespace(start + s_lz___dd) + + ) + { + year = leapYear; + month = firstMonth; + typeCode = DateTimeTypeCode.GDay; + return true; + } + } + return false; + } + + private static bool Test(XsdDateTimeFlags left, XsdDateTimeFlags right) + { + return (left & right) != 0; + } + + private bool Parse2Dig(int start, ref int num) + { + if (start + 1 < _length) + { + int d2 = _text[start] - '0'; + int d1 = _text[start + 1] - '0'; + if (0 <= d2 && d2 < 10 && + 0 <= d1 && d1 < 10 + ) + { + num = d2 * 10 + d1; + return true; + } + } + return false; + } + + private bool Parse4Dig(int start, ref int num) + { + if (start + 3 < _length) + { + int d4 = _text[start] - '0'; + int d3 = _text[start + 1] - '0'; + int d2 = _text[start + 2] - '0'; + int d1 = _text[start + 3] - '0'; + if (0 <= d4 && d4 < 10 && + 0 <= d3 && d3 < 10 && + 0 <= d2 && d2 < 10 && + 0 <= d1 && d1 < 10 + ) + { + num = ((d4 * 10 + d3) * 10 + d2) * 10 + d1; + return true; + } + } + return false; + } + + private bool ParseChar(int start, char ch) + { + return start < _length && _text[start] == ch; + } + + private bool ParseDate(int start) + { + return + Parse4Dig(start, ref year) && 1 <= year && + ParseChar(start + s_lzyyyy, '-') && + Parse2Dig(start + s_lzyyyy_, ref month) && 1 <= month && month <= 12 && + ParseChar(start + s_lzyyyy_MM, '-') && + Parse2Dig(start + s_lzyyyy_MM_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(year, month); + } + + private bool ParseTime(ref int start) + { + if ( + Parse2Dig(start, ref hour) && hour < 24 && + ParseChar(start + s_lzHH, ':') && + Parse2Dig(start + s_lzHH_, ref minute) && minute < 60 && + ParseChar(start + s_lzHH_mm, ':') && + Parse2Dig(start + s_lzHH_mm_, ref second) && second < 60 + ) + { + start += s_lzHH_mm_ss; + if (ParseChar(start, '.')) + { + // Parse factional part of seconds + // We allow any number of digits, but keep only first 7 + this.fraction = 0; + int fractionDigits = 0; + int round = 0; + while (++start < _length) + { + int d = _text[start] - '0'; + if (9u < unchecked((uint)d)) + { // d < 0 || 9 < d + break; + } + if (fractionDigits < MaxFractionDigits) + { + this.fraction = (this.fraction * 10) + d; + } + else if (fractionDigits == MaxFractionDigits) + { + if (5 < d) + { + round = 1; + } + else if (d == 5) + { + round = -1; + } + } + else if (round < 0 && d != 0) + { + round = 1; + } + fractionDigits++; + } + if (fractionDigits < MaxFractionDigits) + { + if (fractionDigits == 0) + { + return false; // cannot end with . + } + fraction *= Power10[MaxFractionDigits - fractionDigits]; + } + else + { + if (round < 0) + { + round = fraction & 1; + } + fraction += round; + } + } + return true; + } + // cleanup - conflict with gYear + hour = 0; + return false; + } + + private bool ParseTimeAndWhitespace(int start) + { + if (ParseTime(ref start)) + { + while (start < _length) + {//&& char.IsWhiteSpace(text[start])) { + start++; + } + return start == _length; + } + return false; + } + + private bool ParseTimeAndZoneAndWhitespace(int start) + { + if (ParseTime(ref start)) + { + if (ParseZoneAndWhitespace(start)) + { + return true; + } + } + return false; + } + + private bool ParseZoneAndWhitespace(int start) + { + if (start < _length) + { + char ch = _text[start]; + if (ch == 'Z' || ch == 'z') + { + kind = XsdDateTimeKind.Zulu; + start++; + } + else if (start + 5 < _length) + { + if ( + Parse2Dig(start + s_Lz_, ref zoneHour) && zoneHour <= 99 && + ParseChar(start + s_lz_zz, ':') && + Parse2Dig(start + s_lz_zz_, ref zoneMinute) && zoneMinute <= 99 + ) + { + if (ch == '-') + { + kind = XsdDateTimeKind.LocalWestOfZulu; + start += s_lz_zz_zz; + } + else if (ch == '+') + { + kind = XsdDateTimeKind.LocalEastOfZulu; + start += s_lz_zz_zz; + } + } + } + } + while (start < _length && char.IsWhiteSpace(_text[start])) + { + start++; + } + return start == _length; + } + } +} diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/DateTimeTypeCode.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/DateTimeTypeCode.cs new file mode 100644 index 00000000000000..71750fbd57b07e --- /dev/null +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/DateTimeTypeCode.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Xml.Schema.DateAndTime.Specifications +{ + /// + /// Subset of represented XML Schema types. + /// + internal enum DateTimeTypeCode + { + DateTime, + Time, + Date, + GYearMonth, + GYear, + GMonthDay, + GDay, + GMonth, + XdrDateTime, + } +} diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/XsdDateTimeKind.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/XsdDateTimeKind.cs new file mode 100644 index 00000000000000..b168b257ab284a --- /dev/null +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/XsdDateTimeKind.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Xml.Schema.DateAndTime.Specifications +{ + /// + /// Internal representation of . + /// + internal enum XsdDateTimeKind + { + Unspecified, + Zulu, + LocalWestOfZulu, // GMT-1..14, N..Y + LocalEastOfZulu // GMT+1..14, A..M + } +} diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs index c8c301cf323830..faf38890dd447d 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs @@ -1,11 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Diagnostics; using System.Numerics; using System.Text; -using System.Xml; +using System.Xml.Schema.DateAndTime.Parsers; +using System.Xml.Schema.DateAndTime.Specifications; namespace System.Xml.Schema { @@ -48,30 +48,6 @@ internal struct XsdDateTime // 7-0 Zone Minutes private uint _extra; - - // Subset of XML Schema types XsdDateTime represents - private enum DateTimeTypeCode - { - DateTime, - Time, - Date, - GYearMonth, - GYear, - GMonthDay, - GDay, - GMonth, - XdrDateTime, - } - - // Internal representation of DateTimeKind - private enum XsdDateTimeKind - { - Unspecified, - Zulu, - LocalWestOfZulu, // GMT-1..14, N..Y - LocalEastOfZulu // GMT+1..14, A..M - } - // Masks and shifts used for packing and unpacking extra private const uint TypeMask = 0xFF000000; private const uint KindMask = 0x00FF0000; @@ -81,33 +57,8 @@ private enum XsdDateTimeKind private const int KindShift = 16; private const int ZoneHourShift = 8; - // Maximum number of fraction digits; - private const short MaxFractionDigits = 7; private const int TicksToFractionDivisor = 10000000; - private static readonly int s_lzyyyy = "yyyy".Length; - private static readonly int s_lzyyyy_ = "yyyy-".Length; - private static readonly int s_lzyyyy_MM = "yyyy-MM".Length; - private static readonly int s_lzyyyy_MM_ = "yyyy-MM-".Length; - private static readonly int s_lzyyyy_MM_dd = "yyyy-MM-dd".Length; - private static readonly int s_lzyyyy_MM_ddT = "yyyy-MM-ddT".Length; - private static readonly int s_lzHH = "HH".Length; - private static readonly int s_lzHH_ = "HH:".Length; - private static readonly int s_lzHH_mm = "HH:mm".Length; - private static readonly int s_lzHH_mm_ = "HH:mm:".Length; - private static readonly int s_lzHH_mm_ss = "HH:mm:ss".Length; - private static readonly int s_Lz_ = "-".Length; - private static readonly int s_lz_zz = "-zz".Length; - private static readonly int s_lz_zz_ = "-zz:".Length; - private static readonly int s_lz_zz_zz = "-zz:zz".Length; - private static readonly int s_Lz__ = "--".Length; - private static readonly int s_lz__mm = "--MM".Length; - private static readonly int s_lz__mm_ = "--MM-".Length; - private static readonly int s_lz__mm__ = "--MM--".Length; - private static readonly int s_lz__mm_dd = "--MM-dd".Length; - private static readonly int s_Lz___ = "---".Length; - private static readonly int s_lz___dd = "---dd".Length; - // Number of days in a non-leap year private const int DaysPerYear = 365; // Number of days in 4 years @@ -127,7 +78,7 @@ private enum XsdDateTimeKind /// public XsdDateTime(string text, XsdDateTimeFlags kinds) : this() { - Parser parser = default; + XsdDateTimeParser parser = default; if (!parser.Parse(text, kinds)) { throw new FormatException(SR.Format(SR.XmlConvert_BadFormat, text, kinds)); @@ -135,12 +86,12 @@ public XsdDateTime(string text, XsdDateTimeFlags kinds) : this() InitiateXsdDateTime(parser); } - private XsdDateTime(Parser parser) : this() + private XsdDateTime(XsdDateTimeParser parser) : this() { InitiateXsdDateTime(parser); } - private void InitiateXsdDateTime(Parser parser) + private void InitiateXsdDateTime(XsdDateTimeParser parser) { _dt = new DateTime(parser.year, parser.month, parser.day, parser.hour, parser.minute, parser.second); if (parser.fraction != 0) @@ -152,7 +103,7 @@ private void InitiateXsdDateTime(Parser parser) internal static bool TryParse(string text, XsdDateTimeFlags kinds, out XsdDateTime result) { - Parser parser = default; + XsdDateTimeParser parser = default; if (!parser.Parse(text, kinds)) { result = default; @@ -543,14 +494,14 @@ public bool TryFormat(Span destination, out int charsWritten) // Serialize year, month and day private void PrintDate(ref ValueStringBuilder vsb) { - Span text = vsb.AppendSpan(s_lzyyyy_MM_dd); + Span text = vsb.AppendSpan(XsdDateTimeParser.s_lzyyyy_MM_dd); int year, month, day; GetYearMonthDay(out year, out month, out day); WriteXDigits(text, 0, year, 4); - text[s_lzyyyy] = '-'; - Write2Digits(text, s_lzyyyy_, month); - text[s_lzyyyy_MM] = '-'; - Write2Digits(text, s_lzyyyy_MM_, day); + text[XsdDateTimeParser.s_lzyyyy] = '-'; + Write2Digits(text, XsdDateTimeParser.s_lzyyyy_, month); + text[XsdDateTimeParser.s_lzyyyy_MM] = '-'; + Write2Digits(text, XsdDateTimeParser.s_lzyyyy_MM_, day); } // When printing the date, we need the year, month and the day. When @@ -609,16 +560,16 @@ private void GetYearMonthDay(out int year, out int month, out int day) // Serialize hour, minute, second and fraction private void PrintTime(ref ValueStringBuilder vsb) { - Span text = vsb.AppendSpan(s_lzHH_mm_ss); + Span text = vsb.AppendSpan(XsdDateTimeParser.s_lzHH_mm_ss); Write2Digits(text, 0, Hour); - text[s_lzHH] = ':'; - Write2Digits(text, s_lzHH_, Minute); - text[s_lzHH_mm] = ':'; - Write2Digits(text, s_lzHH_mm_, Second); + text[XsdDateTimeParser.s_lzHH] = ':'; + Write2Digits(text, XsdDateTimeParser.s_lzHH_, Minute); + text[XsdDateTimeParser.s_lzHH_mm] = ':'; + Write2Digits(text, XsdDateTimeParser.s_lzHH_mm_, Second); int fraction = Fraction; if (fraction != 0) { - int fractionDigits = MaxFractionDigits; + int fractionDigits = XsdDateTimeParser.MaxFractionDigits; while (fraction % 10 == 0) { fractionDigits--; @@ -641,18 +592,18 @@ private void PrintZone(ref ValueStringBuilder vsb) vsb.Append('Z'); break; case XsdDateTimeKind.LocalWestOfZulu: - text = vsb.AppendSpan(s_lz_zz_zz); + text = vsb.AppendSpan(XsdDateTimeParser.s_lz_zz_zz); text[0] = '-'; - Write2Digits(text, s_Lz_, ZoneHour); - text[s_lz_zz] = ':'; - Write2Digits(text, s_lz_zz_, ZoneMinute); + Write2Digits(text, XsdDateTimeParser.s_Lz_, ZoneHour); + text[XsdDateTimeParser.s_lz_zz] = ':'; + Write2Digits(text, XsdDateTimeParser.s_lz_zz_, ZoneMinute); break; case XsdDateTimeKind.LocalEastOfZulu: - text = vsb.AppendSpan(s_lz_zz_zz); + text = vsb.AppendSpan(XsdDateTimeParser.s_lz_zz_zz); text[0] = '+'; - Write2Digits(text, s_Lz_, ZoneHour); - text[s_lz_zz] = ':'; - Write2Digits(text, s_lz_zz_, ZoneMinute); + Write2Digits(text, XsdDateTimeParser.s_Lz_, ZoneHour); + text[XsdDateTimeParser.s_lz_zz] = ':'; + Write2Digits(text, XsdDateTimeParser.s_lz_zz_, ZoneMinute); break; default: // do nothing @@ -688,388 +639,5 @@ private static void Write2Digits(Span text, int start, int value) XmlTypeCode.GDay, XmlTypeCode.GMonth }; - - - // Parsing string according to XML schema spec - private struct Parser - { - private const int leapYear = 1904; - private const int firstMonth = 1; - private const int firstDay = 1; - - public DateTimeTypeCode typeCode; - public int year; - public int month; - public int day; - public int hour; - public int minute; - public int second; - public int fraction; - public XsdDateTimeKind kind; - public int zoneHour; - public int zoneMinute; - - private string _text; - private int _length; - - public bool Parse(string text, XsdDateTimeFlags kinds) - { - _text = text; - _length = text.Length; - - // Skip leading whitespace - int start = 0; - while (start < _length && char.IsWhiteSpace(text[start])) - { - start++; - } - // Choose format starting from the most common and trying not to reparse the same thing too many times - if (Test(kinds, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.Date | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrDateTimeNoTz)) - { - if (ParseDate(start)) - { - if (Test(kinds, XsdDateTimeFlags.DateTime)) - { - if (ParseChar(start + s_lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT)) - { - typeCode = DateTimeTypeCode.DateTime; - return true; - } - } - if (Test(kinds, XsdDateTimeFlags.Date)) - { - if (ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd)) - { - typeCode = DateTimeTypeCode.Date; - return true; - } - } - - if (Test(kinds, XsdDateTimeFlags.XdrDateTime)) - { - if (ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd) || (ParseChar(start + s_lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT))) - { - typeCode = DateTimeTypeCode.XdrDateTime; - return true; - } - } - if (Test(kinds, XsdDateTimeFlags.XdrDateTimeNoTz)) - { - if (ParseChar(start + s_lzyyyy_MM_dd, 'T')) - { - if (ParseTimeAndWhitespace(start + s_lzyyyy_MM_ddT)) - { - typeCode = DateTimeTypeCode.XdrDateTime; - return true; - } - } - else - { - typeCode = DateTimeTypeCode.XdrDateTime; - return true; - } - } - } - } - - if (Test(kinds, XsdDateTimeFlags.Time)) - { - if (ParseTimeAndZoneAndWhitespace(start)) - { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time - year = leapYear; - month = firstMonth; - day = firstDay; - typeCode = DateTimeTypeCode.Time; - return true; - } - } - - if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz)) - { - if (ParseTimeAndWhitespace(start)) - { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time - year = leapYear; - month = firstMonth; - day = firstDay; - typeCode = DateTimeTypeCode.Time; - return true; - } - } - - if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear)) - { - if (Parse4Dig(start, ref year) && 1 <= year) - { - if (Test(kinds, XsdDateTimeFlags.GYearMonth)) - { - if ( - ParseChar(start + s_lzyyyy, '-') && - Parse2Dig(start + s_lzyyyy_, ref month) && 1 <= month && month <= 12 && - ParseZoneAndWhitespace(start + s_lzyyyy_MM) - ) - { - day = firstDay; - typeCode = DateTimeTypeCode.GYearMonth; - return true; - } - } - if (Test(kinds, XsdDateTimeFlags.GYear)) - { - if (ParseZoneAndWhitespace(start + s_lzyyyy)) - { - month = firstMonth; - day = firstDay; - typeCode = DateTimeTypeCode.GYear; - return true; - } - } - } - } - if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth)) - { - if ( - ParseChar(start, '-') && - ParseChar(start + s_Lz_, '-') && - Parse2Dig(start + s_Lz__, ref month) && 1 <= month && month <= 12 - ) - { - if (Test(kinds, XsdDateTimeFlags.GMonthDay) && ParseChar(start + s_lz__mm, '-')) - { - if ( - Parse2Dig(start + s_lz__mm_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month) && - ParseZoneAndWhitespace(start + s_lz__mm_dd) - ) - { - year = leapYear; - typeCode = DateTimeTypeCode.GMonthDay; - return true; - } - } - if (Test(kinds, XsdDateTimeFlags.GMonth)) - { - if (ParseZoneAndWhitespace(start + s_lz__mm) || (ParseChar(start + s_lz__mm, '-') && ParseChar(start + s_lz__mm_, '-') && ParseZoneAndWhitespace(start + s_lz__mm__))) - { - year = leapYear; - day = firstDay; - typeCode = DateTimeTypeCode.GMonth; - return true; - } - } - } - } - if (Test(kinds, XsdDateTimeFlags.GDay)) - { - if ( - ParseChar(start, '-') && - ParseChar(start + s_Lz_, '-') && - ParseChar(start + s_Lz__, '-') && - Parse2Dig(start + s_Lz___, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, firstMonth) && - ParseZoneAndWhitespace(start + s_lz___dd) - - ) - { - year = leapYear; - month = firstMonth; - typeCode = DateTimeTypeCode.GDay; - return true; - } - } - return false; - } - - - private bool ParseDate(int start) - { - return - Parse4Dig(start, ref year) && 1 <= year && - ParseChar(start + s_lzyyyy, '-') && - Parse2Dig(start + s_lzyyyy_, ref month) && 1 <= month && month <= 12 && - ParseChar(start + s_lzyyyy_MM, '-') && - Parse2Dig(start + s_lzyyyy_MM_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(year, month); - } - - private bool ParseTimeAndZoneAndWhitespace(int start) - { - if (ParseTime(ref start)) - { - if (ParseZoneAndWhitespace(start)) - { - return true; - } - } - return false; - } - - private bool ParseTimeAndWhitespace(int start) - { - if (ParseTime(ref start)) - { - while (start < _length) - {//&& char.IsWhiteSpace(text[start])) { - start++; - } - return start == _length; - } - return false; - } - - private static ReadOnlySpan Power10 => [-1, 10, 100, 1000, 10000, 100000, 1000000]; - private bool ParseTime(ref int start) - { - if ( - Parse2Dig(start, ref hour) && hour < 24 && - ParseChar(start + s_lzHH, ':') && - Parse2Dig(start + s_lzHH_, ref minute) && minute < 60 && - ParseChar(start + s_lzHH_mm, ':') && - Parse2Dig(start + s_lzHH_mm_, ref second) && second < 60 - ) - { - start += s_lzHH_mm_ss; - if (ParseChar(start, '.')) - { - // Parse factional part of seconds - // We allow any number of digits, but keep only first 7 - this.fraction = 0; - int fractionDigits = 0; - int round = 0; - while (++start < _length) - { - int d = _text[start] - '0'; - if (9u < unchecked((uint)d)) - { // d < 0 || 9 < d - break; - } - if (fractionDigits < MaxFractionDigits) - { - this.fraction = (this.fraction * 10) + d; - } - else if (fractionDigits == MaxFractionDigits) - { - if (5 < d) - { - round = 1; - } - else if (d == 5) - { - round = -1; - } - } - else if (round < 0 && d != 0) - { - round = 1; - } - fractionDigits++; - } - if (fractionDigits < MaxFractionDigits) - { - if (fractionDigits == 0) - { - return false; // cannot end with . - } - fraction *= Power10[MaxFractionDigits - fractionDigits]; - } - else - { - if (round < 0) - { - round = fraction & 1; - } - fraction += round; - } - } - return true; - } - // cleanup - conflict with gYear - hour = 0; - return false; - } - - private bool ParseZoneAndWhitespace(int start) - { - if (start < _length) - { - char ch = _text[start]; - if (ch == 'Z' || ch == 'z') - { - kind = XsdDateTimeKind.Zulu; - start++; - } - else if (start + 5 < _length) - { - if ( - Parse2Dig(start + s_Lz_, ref zoneHour) && zoneHour <= 99 && - ParseChar(start + s_lz_zz, ':') && - Parse2Dig(start + s_lz_zz_, ref zoneMinute) && zoneMinute <= 99 - ) - { - if (ch == '-') - { - kind = XsdDateTimeKind.LocalWestOfZulu; - start += s_lz_zz_zz; - } - else if (ch == '+') - { - kind = XsdDateTimeKind.LocalEastOfZulu; - start += s_lz_zz_zz; - } - } - } - } - while (start < _length && char.IsWhiteSpace(_text[start])) - { - start++; - } - return start == _length; - } - - - private bool Parse4Dig(int start, ref int num) - { - if (start + 3 < _length) - { - int d4 = _text[start] - '0'; - int d3 = _text[start + 1] - '0'; - int d2 = _text[start + 2] - '0'; - int d1 = _text[start + 3] - '0'; - if (0 <= d4 && d4 < 10 && - 0 <= d3 && d3 < 10 && - 0 <= d2 && d2 < 10 && - 0 <= d1 && d1 < 10 - ) - { - num = ((d4 * 10 + d3) * 10 + d2) * 10 + d1; - return true; - } - } - return false; - } - - private bool Parse2Dig(int start, ref int num) - { - if (start + 1 < _length) - { - int d2 = _text[start] - '0'; - int d1 = _text[start + 1] - '0'; - if (0 <= d2 && d2 < 10 && - 0 <= d1 && d1 < 10 - ) - { - num = d2 * 10 + d1; - return true; - } - } - return false; - } - - private bool ParseChar(int start, char ch) - { - return start < _length && _text[start] == ch; - } - - private static bool Test(XsdDateTimeFlags left, XsdDateTimeFlags right) - { - return (left & right) != 0; - } - } } } From dea09ad1e8f74ea1e794373899800a47ffa8d409 Mon Sep 17 00:00:00 2001 From: Maksim Golev Date: Mon, 31 Mar 2025 14:59:17 +0400 Subject: [PATCH 07/10] refactor(#93189): Refactoring of "XsdDateTimeParser". --- .../DateAndTime/Parsers/XsdDateTimeParser.cs | 252 ++++++++---------- 1 file changed, 114 insertions(+), 138 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs index c450c45aeae0f8..11e90b5a74b0f9 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs @@ -56,6 +56,11 @@ internal struct XsdDateTimeParser public bool Parse(string text, XsdDateTimeFlags kinds) { + const XsdDateTimeFlags dateVariants = XsdDateTimeFlags.DateTime + | XsdDateTimeFlags.Date + | XsdDateTimeFlags.XdrDateTime + | XsdDateTimeFlags.XdrDateTimeNoTz; + _text = text; _length = text.Length; @@ -65,157 +70,133 @@ public bool Parse(string text, XsdDateTimeFlags kinds) { start++; } + // Choose format starting from the most common and trying not to reparse the same thing too many times - if (Test(kinds, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.Date | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrDateTimeNoTz)) + if (Test(kinds, dateVariants) && ParseDate(start)) { - if (ParseDate(start)) + if (Test(kinds, XsdDateTimeFlags.DateTime) && + ParseChar(start + s_lzyyyy_MM_dd, 'T') && + ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT)) { - if (Test(kinds, XsdDateTimeFlags.DateTime)) - { - if (ParseChar(start + s_lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT)) - { - typeCode = DateTimeTypeCode.DateTime; - return true; - } - } - if (Test(kinds, XsdDateTimeFlags.Date)) - { - if (ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd)) - { - typeCode = DateTimeTypeCode.Date; - return true; - } - } + typeCode = DateTimeTypeCode.DateTime; + return true; + } + + if (Test(kinds, XsdDateTimeFlags.Date) + && ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd)) + { + typeCode = DateTimeTypeCode.Date; + return true; + } + + if (Test(kinds, XsdDateTimeFlags.XdrDateTime) && + (ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd) || + (ParseChar(start + s_lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT)))) + { + typeCode = DateTimeTypeCode.XdrDateTime; + return true; + } - if (Test(kinds, XsdDateTimeFlags.XdrDateTime)) + if (Test(kinds, XsdDateTimeFlags.XdrDateTimeNoTz)) + { + if (ParseChar(start + s_lzyyyy_MM_dd, 'T')) { - if (ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd) || (ParseChar(start + s_lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT))) + if (ParseTimeAndWhitespace(start + s_lzyyyy_MM_ddT)) { typeCode = DateTimeTypeCode.XdrDateTime; return true; } } - if (Test(kinds, XsdDateTimeFlags.XdrDateTimeNoTz)) + else { - if (ParseChar(start + s_lzyyyy_MM_dd, 'T')) - { - if (ParseTimeAndWhitespace(start + s_lzyyyy_MM_ddT)) - { - typeCode = DateTimeTypeCode.XdrDateTime; - return true; - } - } - else - { - typeCode = DateTimeTypeCode.XdrDateTime; - return true; - } + typeCode = DateTimeTypeCode.XdrDateTime; + return true; } } } - if (Test(kinds, XsdDateTimeFlags.Time)) + if (Test(kinds, XsdDateTimeFlags.Time) && ParseTimeAndZoneAndWhitespace(start)) { - if (ParseTimeAndZoneAndWhitespace(start)) - { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time - year = leapYear; - month = firstMonth; - day = firstDay; - typeCode = DateTimeTypeCode.Time; - return true; - } + year = leapYear; + month = firstMonth; + day = firstDay; + typeCode = DateTimeTypeCode.Time; + return true; } - if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz)) + if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz) && ParseTimeAndWhitespace(start)) { - if (ParseTimeAndWhitespace(start)) - { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time - year = leapYear; - month = firstMonth; + year = leapYear; + month = firstMonth; + day = firstDay; + typeCode = DateTimeTypeCode.Time; + return true; + } + + if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear) && Parse4Dig(start, ref year) && 1 <= year) + { + if (Test(kinds, XsdDateTimeFlags.GYearMonth) && + ParseChar(start + s_lzyyyy, '-') && + Parse2Dig(start + s_lzyyyy_, ref month) && 1 <= month && month <= 12 && + ParseZoneAndWhitespace(start + s_lzyyyy_MM)) + { day = firstDay; - typeCode = DateTimeTypeCode.Time; + typeCode = DateTimeTypeCode.GYearMonth; return true; } - } - if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear)) - { - if (Parse4Dig(start, ref year) && 1 <= year) + if (Test(kinds, XsdDateTimeFlags.GYear) && ParseZoneAndWhitespace(start + s_lzyyyy)) { - if (Test(kinds, XsdDateTimeFlags.GYearMonth)) - { - if ( - ParseChar(start + s_lzyyyy, '-') && - Parse2Dig(start + s_lzyyyy_, ref month) && 1 <= month && month <= 12 && - ParseZoneAndWhitespace(start + s_lzyyyy_MM) - ) - { - day = firstDay; - typeCode = DateTimeTypeCode.GYearMonth; - return true; - } - } - if (Test(kinds, XsdDateTimeFlags.GYear)) - { - if (ParseZoneAndWhitespace(start + s_lzyyyy)) - { - month = firstMonth; - day = firstDay; - typeCode = DateTimeTypeCode.GYear; - return true; - } - } + month = firstMonth; + day = firstDay; + typeCode = DateTimeTypeCode.GYear; + return true; } } - if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth)) + + if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth) && + ParseChar(start, '-') && + ParseChar(start + s_Lz_, '-') && + Parse2Dig(start + s_Lz__, ref month) && 1 <= month && month <= 12) { - if ( - ParseChar(start, '-') && - ParseChar(start + s_Lz_, '-') && - Parse2Dig(start + s_Lz__, ref month) && 1 <= month && month <= 12 - ) + if (Test(kinds, XsdDateTimeFlags.GMonthDay) && + ParseChar(start + s_lz__mm, '-') && + Parse2Dig(start + s_lz__mm_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month) && + ParseZoneAndWhitespace(start + s_lz__mm_dd)) { - if (Test(kinds, XsdDateTimeFlags.GMonthDay) && ParseChar(start + s_lz__mm, '-')) - { - if ( - Parse2Dig(start + s_lz__mm_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month) && - ParseZoneAndWhitespace(start + s_lz__mm_dd) - ) - { - year = leapYear; - typeCode = DateTimeTypeCode.GMonthDay; - return true; - } - } - if (Test(kinds, XsdDateTimeFlags.GMonth)) - { - if (ParseZoneAndWhitespace(start + s_lz__mm) || (ParseChar(start + s_lz__mm, '-') && ParseChar(start + s_lz__mm_, '-') && ParseZoneAndWhitespace(start + s_lz__mm__))) - { - year = leapYear; - day = firstDay; - typeCode = DateTimeTypeCode.GMonth; - return true; - } - } + year = leapYear; + typeCode = DateTimeTypeCode.GMonthDay; + return true; } - } - if (Test(kinds, XsdDateTimeFlags.GDay)) - { - if ( - ParseChar(start, '-') && - ParseChar(start + s_Lz_, '-') && - ParseChar(start + s_Lz__, '-') && - Parse2Dig(start + s_Lz___, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, firstMonth) && - ParseZoneAndWhitespace(start + s_lz___dd) - ) + if (Test(kinds, XsdDateTimeFlags.GMonth) && + (ParseZoneAndWhitespace(start + s_lz__mm) || + (ParseChar(start + s_lz__mm, '-') && + ParseChar(start + s_lz__mm_, '-') && + ParseZoneAndWhitespace(start + s_lz__mm__)))) { year = leapYear; - month = firstMonth; - typeCode = DateTimeTypeCode.GDay; + day = firstDay; + typeCode = DateTimeTypeCode.GMonth; return true; } } + + if (Test(kinds, XsdDateTimeFlags.GDay) && + ParseChar(start, '-') && + ParseChar(start + s_Lz_, '-') && + ParseChar(start + s_Lz__, '-') && + Parse2Dig(start + s_Lz___, ref day) && + 1 <= day && + day <= DateTime.DaysInMonth(leapYear, firstMonth) && + ParseZoneAndWhitespace(start + s_lz___dd)) + { + year = leapYear; + month = firstMonth; + typeCode = DateTimeTypeCode.GDay; + return true; + } + return false; } @@ -352,7 +333,7 @@ private bool ParseTimeAndWhitespace(int start) if (ParseTime(ref start)) { while (start < _length) - {//&& char.IsWhiteSpace(text[start])) { + { start++; } return start == _length; @@ -362,12 +343,9 @@ private bool ParseTimeAndWhitespace(int start) private bool ParseTimeAndZoneAndWhitespace(int start) { - if (ParseTime(ref start)) + if (ParseTime(ref start) && ParseZoneAndWhitespace(start)) { - if (ParseZoneAndWhitespace(start)) - { - return true; - } + return true; } return false; } @@ -382,24 +360,22 @@ private bool ParseZoneAndWhitespace(int start) kind = XsdDateTimeKind.Zulu; start++; } - else if (start + 5 < _length) + else if ((start + 5 < _length) && + Parse2Dig(start + s_Lz_, ref zoneHour) && + zoneHour <= 99 && + ParseChar(start + s_lz_zz, ':') && + Parse2Dig(start + s_lz_zz_, ref zoneMinute) && + zoneMinute <= 99) { - if ( - Parse2Dig(start + s_Lz_, ref zoneHour) && zoneHour <= 99 && - ParseChar(start + s_lz_zz, ':') && - Parse2Dig(start + s_lz_zz_, ref zoneMinute) && zoneMinute <= 99 - ) + if (ch == '-') { - if (ch == '-') - { - kind = XsdDateTimeKind.LocalWestOfZulu; - start += s_lz_zz_zz; - } - else if (ch == '+') - { - kind = XsdDateTimeKind.LocalEastOfZulu; - start += s_lz_zz_zz; - } + kind = XsdDateTimeKind.LocalWestOfZulu; + start += s_lz_zz_zz; + } + else if (ch == '+') + { + kind = XsdDateTimeKind.LocalEastOfZulu; + start += s_lz_zz_zz; } } } From 0f4201c3c776e02e621fbf3122e33b9fb6b306a7 Mon Sep 17 00:00:00 2001 From: Maksim Golev Date: Tue, 1 Apr 2025 08:22:35 +0400 Subject: [PATCH 08/10] refactor(#93189): Renaming "System.Xml.Schema.DateAndTime.Parsers.XsdDateTimeParser" to "System.Xml.Schema.DateAndTime.Converters.DateAndTimeConverter" and change it type from struct to static class. --- .../src/System.Private.Xml.csproj | 3 +- .../Converters/DateAndTimeConverter.cs | 572 ++++++++++++++++++ .../DateAndTime/Helpers/DateAndTimeInfo.cs | 64 ++ .../DateAndTime/Parsers/XsdDateTimeParser.cs | 389 ------------ .../src/System/Xml/Schema/XsdDateTime.cs | 67 +- 5 files changed, 672 insertions(+), 423 deletions(-) create mode 100644 src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs create mode 100644 src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs delete mode 100644 src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs diff --git a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj index a4cd146465a1c9..de69de4b2f81f8 100644 --- a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj +++ b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj @@ -42,8 +42,9 @@ True TextUtf8RawTextWriter.tt + + - TextTemplatingFileGenerator diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs new file mode 100644 index 00000000000000..281d8b995c9a0d --- /dev/null +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs @@ -0,0 +1,572 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Xml.Schema.DateAndTime.Helpers; +using System.Xml.Schema.DateAndTime.Specifications; + +namespace System.Xml.Schema.DateAndTime.Converters +{ + internal static class DateAndTimeConverter + { + /// + /// Maximum number of fraction digits. + /// + public const short MaxFractionDigits = 7; + private const int firstDay = 1; + private const int firstMonth = 1; + private const int leapYear = 1904; + + public static readonly int s_Lz_ = "-".Length; + public static readonly int s_lz_zz = "-zz".Length; + public static readonly int s_lz_zz_ = "-zz:".Length; + public static readonly int s_lz_zz_zz = "-zz:zz".Length; + public static readonly int s_lzHH = "HH".Length; + public static readonly int s_lzHH_ = "HH:".Length; + public static readonly int s_lzHH_mm = "HH:mm".Length; + public static readonly int s_lzHH_mm_ = "HH:mm:".Length; + public static readonly int s_lzHH_mm_ss = "HH:mm:ss".Length; + public static readonly int s_lzyyyy = "yyyy".Length; + public static readonly int s_lzyyyy_ = "yyyy-".Length; + public static readonly int s_lzyyyy_MM = "yyyy-MM".Length; + public static readonly int s_lzyyyy_MM_ = "yyyy-MM-".Length; + public static readonly int s_lzyyyy_MM_dd = "yyyy-MM-dd".Length; + private static readonly int s_Lz__ = "--".Length; + private static readonly int s_Lz___ = "---".Length; + private static readonly int s_lz___dd = "---dd".Length; + private static readonly int s_lz__mm = "--MM".Length; + private static readonly int s_lz__mm_ = "--MM-".Length; + private static readonly int s_lz__mm__ = "--MM--".Length; + private static readonly int s_lz__mm_dd = "--MM-dd".Length; + private static readonly int s_lzyyyy_MM_ddT = "yyyy-MM-ddT".Length; + + private static ReadOnlySpan Power10 => [-1, 10, 100, 1000, 10000, 100000, 1000000]; + + public static bool TryParse(string text, XsdDateTimeFlags kinds, out DateAndTimeInfo parsedValue) + { + // Skip leading whitespace + int start = 0; + while (start < text.Length && char.IsWhiteSpace(text[start])) + { + start++; + } + + if (TryParseAsDate(text, kinds, start, out parsedValue)) + { + return true; + } + + if (TryParseAsTime(text, kinds, start, out parsedValue)) + { + return true; + } + + if (TryParseAsXdrTimeNoTz(text, kinds, start, out parsedValue)) + { + return true; + } + + if (TryParseAsGYearOrGYearMonth(text, kinds, start, out parsedValue)) + { + return true; + } + + if (TryParseAsGMonthOrGMonthDay(text, kinds, start, out parsedValue)) + { + return true; + } + + if (TryParseAsGDay(text, kinds, start, out parsedValue)) + { + return true; + } + + return false; + } + + private static bool TryParseAsDate( + string text, + XsdDateTimeFlags kinds, + int start, + out DateAndTimeInfo parsedValue) + { + const XsdDateTimeFlags dateVariants = XsdDateTimeFlags.DateTime + | XsdDateTimeFlags.Date + | XsdDateTimeFlags.XdrDateTime + | XsdDateTimeFlags.XdrDateTimeNoTz; + + int? year, month, day, hour, minute, second, fraction, zoneHour, zoneMinute; + XsdDateTimeKind? kind; + + // Choose format starting from the most common and trying not to reparse the same thing too many times + if (Test(kinds, dateVariants) && TryParseDate(text, start, out year, out month, out day)) + { + if (Test(kinds, XsdDateTimeFlags.DateTime) && + ParseChar(text, start + s_lzyyyy_MM_dd, 'T') && + TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out hour, out minute, out second, out fraction, out kind, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(day.Value, fraction.Value, hour.Value, kind.Value, minute.Value, month.Value, second.Value, DateTimeTypeCode.DateTime, year.Value, zoneHour.Value, zoneMinute.Value); + return true; + } + + if (Test(kinds, XsdDateTimeFlags.Date) + && TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM_dd, out kind, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(day.Value, 0, 0, kind.Value, 0, month.Value, 0, DateTimeTypeCode.Date, year.Value, zoneHour.Value, zoneMinute.Value); + return true; + } + + if (Test(kinds, XsdDateTimeFlags.XdrDateTime)) + { + if (TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM_dd, out kind, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(day.Value, 0, 0, kind.Value, 0, month.Value, 0, DateTimeTypeCode.XdrDateTime, year.Value, zoneHour.Value, zoneMinute.Value); + return true; + } + + if (ParseChar(text, start + s_lzyyyy_MM_dd, 'T') + && TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out hour, out minute, out second, out fraction, out kind, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(day.Value, fraction.Value, hour.Value, kind.Value, minute.Value, month.Value, second.Value, DateTimeTypeCode.XdrDateTime, year.Value, zoneHour.Value, zoneMinute.Value); + return true; + } + } + + if (Test(kinds, XsdDateTimeFlags.XdrDateTimeNoTz)) + { + if (ParseChar(text, start + s_lzyyyy_MM_dd, 'T')) + { + if (ParseTimeAndWhitespace(text, start + s_lzyyyy_MM_ddT, out hour, out minute, out second, out fraction)) + { + parsedValue = new DateAndTimeInfo(day.Value, fraction.Value, hour.Value, XsdDateTimeKind.Unspecified, minute.Value, month.Value, second.Value, DateTimeTypeCode.XdrDateTime, year.Value, 0, 0); + return true; + } + } + else + { + parsedValue = new DateAndTimeInfo(day.Value, 0, 0, XsdDateTimeKind.Unspecified, 0, month.Value, 0, DateTimeTypeCode.XdrDateTime, year.Value, 0, 0); + return true; + } + } + } + + parsedValue = default; + return false; + } + + private static bool TryParseAsTime( + string text, + XsdDateTimeFlags kinds, + int start, + out DateAndTimeInfo parsedValue) + { + if (Test(kinds, XsdDateTimeFlags.Time) && TryParseTimeAndZoneAndWhitespace(text, start, out int? hour, out int? minute, out int? second, out int? fraction, out XsdDateTimeKind? kind, out int? zoneHour, out int? zoneMinute)) + { + parsedValue = new DateAndTimeInfo(firstDay, fraction.Value, hour.Value, kind.Value, minute.Value, firstMonth, second.Value, DateTimeTypeCode.Time, leapYear, zoneHour.Value, zoneMinute.Value); + return true; + } + + parsedValue = default; + return false; + } + + private static bool TryParseAsXdrTimeNoTz( + string text, + XsdDateTimeFlags kinds, + int start, + out DateAndTimeInfo parsedValue) + { + if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz) && ParseTimeAndWhitespace(text, start, out int? hour, out int? minute, out int? second, out int? fraction)) + { + parsedValue = new DateAndTimeInfo(firstDay, fraction.Value, hour.Value, XsdDateTimeKind.Unspecified, minute.Value, firstMonth, second.Value, DateTimeTypeCode.Time, leapYear, default, default); + return true; + } + + parsedValue = default; + return false; + } + + private static bool TryParseAsGYearOrGYearMonth( + string text, + XsdDateTimeFlags kinds, + int start, + out DateAndTimeInfo parsedValue) + { + int? year, month, zoneHour, zoneMinute; + XsdDateTimeKind? kind; + + if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear) && ParseFourDigits(text, start, out year) && 1 <= year) + { + if (Test(kinds, XsdDateTimeFlags.GYearMonth) && + ParseChar(text, start + s_lzyyyy, '-') && + ParseTwoDigits(text, start + s_lzyyyy_, out month) && + 1 <= month && + month <= 12 && + TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM, out kind, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(firstDay, default, default, kind.Value, default, month.Value, default, DateTimeTypeCode.GYearMonth, year.Value, zoneHour.Value, zoneMinute.Value); + return true; + } + + if (Test(kinds, XsdDateTimeFlags.GYear) && TryParseZoneAndWhitespace(text, start + s_lzyyyy, out kind, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(firstDay, default, default, kind.Value, default, firstMonth, default, DateTimeTypeCode.GYear, year.Value, zoneHour.Value, zoneMinute.Value); + return true; + } + } + + parsedValue = new DateAndTimeInfo(default, default, default, XsdDateTimeKind.Unspecified, default, default, default, default, default, default, default); + return false; + } + + private static bool TryParseAsGMonthOrGMonthDay( + string text, + XsdDateTimeFlags kinds, + int start, + out DateAndTimeInfo parsedValue) + { + int? month, day, zoneHour, zoneMinute; + XsdDateTimeKind? kind; + + if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth) && + ParseChar(text, start, '-') && + ParseChar(text, start + s_Lz_, '-') && + ParseTwoDigits(text, start + s_Lz__, out month) && 1 <= month && month <= 12) + { + if (Test(kinds, XsdDateTimeFlags.GMonthDay) && + ParseChar(text, start + s_lz__mm, '-') && + ParseTwoDigits(text, start + s_lz__mm_, out day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month.Value) && + TryParseZoneAndWhitespace(text, start + s_lz__mm_dd, out kind, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(day.Value, default, default, kind.Value, default, month.Value, default, DateTimeTypeCode.GMonthDay, leapYear, zoneHour.Value, zoneMinute.Value); + return true; + } + + if (Test(kinds, XsdDateTimeFlags.GMonth) && + (TryParseZoneAndWhitespace(text, start + s_lz__mm, out kind, out zoneHour, out zoneMinute) || + ParseChar(text, start + s_lz__mm, '-') && + ParseChar(text, start + s_lz__mm_, '-') && + TryParseZoneAndWhitespace(text, start + s_lz__mm__, out kind, out zoneHour, out zoneMinute))) + { + parsedValue = new DateAndTimeInfo(firstDay, default, default, kind.Value, default, month.Value, default, DateTimeTypeCode.GMonth, leapYear, zoneHour.Value, zoneMinute.Value); + return true; + } + } + + parsedValue = new DateAndTimeInfo(); + return false; + } + + private static bool TryParseAsGDay( + string text, + XsdDateTimeFlags kinds, + int start, + out DateAndTimeInfo parsedValue) + { + int? day, zoneHour, zoneMinute; + XsdDateTimeKind? kind; + + if (Test(kinds, XsdDateTimeFlags.GDay) && + ParseChar(text, start, '-') && + ParseChar(text, start + s_Lz_, '-') && + ParseChar(text, start + s_Lz__, '-') && + ParseTwoDigits(text, start + s_Lz___, out day) && + 1 <= day && + day <= DateTime.DaysInMonth(leapYear, firstMonth) && + TryParseZoneAndWhitespace(text, start + s_lz___dd, out kind, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(day.Value, default, default, kind.Value, default, firstMonth, default, DateTimeTypeCode.GDay, leapYear, zoneHour.Value, zoneMinute.Value); + + return true; + } + + parsedValue = new DateAndTimeInfo(); + return false; + } + + private static bool ParseTwoDigits( + string rawValue, + int start, + [NotNullWhen(true)] out int? num) + { + if (start + 1 < rawValue.Length) + { + int d2 = rawValue[start] - '0'; + int d1 = rawValue[start + 1] - '0'; + if (0 <= d2 && d2 < 10 && + 0 <= d1 && d1 < 10 + ) + { + num = d2 * 10 + d1; + return true; + } + } + + num = default; + return false; + } + + private static bool ParseFourDigits( + string rawValue, + int start, + [NotNullWhen(true)] out int? num) + { + if (start + 3 < rawValue.Length) + { + int d4 = rawValue[start] - '0'; + int d3 = rawValue[start + 1] - '0'; + int d2 = rawValue[start + 2] - '0'; + int d1 = rawValue[start + 3] - '0'; + if (0 <= d4 && d4 < 10 && + 0 <= d3 && d3 < 10 && + 0 <= d2 && d2 < 10 && + 0 <= d1 && d1 < 10 + ) + { + num = ((d4 * 10 + d3) * 10 + d2) * 10 + d1; + return true; + } + } + + num = default; + return false; + } + + private static bool ParseChar(string rawValue, int start, char ch) + { + return start < rawValue.Length && rawValue[start] == ch; + } + + private static bool Test(XsdDateTimeFlags left, XsdDateTimeFlags right) + { + return (left & right) != 0; + } + + private static bool TryParseDate( + string rawValue, + int start, + [NotNullWhen(true)] out int? year, + [NotNullWhen(true)] out int? month, + [NotNullWhen(true)] out int? day) + { + if (ParseFourDigits(rawValue, start, out year) && + 1 <= year && + ParseChar(rawValue, start + s_lzyyyy, '-') && + ParseTwoDigits(rawValue, start + s_lzyyyy_, out month) && + 1 <= month && month <= 12 && + ParseChar(rawValue, start + s_lzyyyy_MM, '-') && + ParseTwoDigits(rawValue, start + s_lzyyyy_MM_, out day) && + 1 <= day && + day <= DateTime.DaysInMonth(year.Value, month.Value) + ) + { + return true; + } + + year = default; + month = default; + day = default; + return false; + } + + private static bool TryParseTime( + string rawValue, + ref int start, + [NotNullWhen(true)] out int? hour, + [NotNullWhen(true)] out int? minute, + [NotNullWhen(true)] out int? second, + [NotNullWhen(true)] out int? fraction) + { + if ( + ParseTwoDigits(rawValue, start, out hour) && hour < 24 && + ParseChar(rawValue, start + s_lzHH, ':') && + ParseTwoDigits(rawValue, start + s_lzHH_, out minute) && minute < 60 && + ParseChar(rawValue, start + s_lzHH_mm, ':') && + ParseTwoDigits(rawValue, start + s_lzHH_mm_, out second) && second < 60 + ) + { + start += s_lzHH_mm_ss; + if (ParseChar(rawValue, start, '.')) + { + // Parse factional part of seconds + // We allow any number of digits, but keep only first 7 + fraction = 0; + int fractionDigits = 0; + int round = 0; + while (++start < rawValue.Length) + { + int d = rawValue[start] - '0'; + if (9u < unchecked((uint)d)) + { // d < 0 || 9 < d + break; + } + if (fractionDigits < MaxFractionDigits) + { + fraction = fraction * 10 + d; + } + else if (fractionDigits == MaxFractionDigits) + { + if (5 < d) + { + round = 1; + } + else if (d == 5) + { + round = -1; + } + } + else if (round < 0 && d != 0) + { + round = 1; + } + fractionDigits++; + } + if (fractionDigits < MaxFractionDigits) + { + if (fractionDigits == 0) + { + return false; // cannot end with . + } + fraction *= Power10[MaxFractionDigits - fractionDigits]; + } + else + { + if (round < 0) + { + round = fraction.Value & 1; + } + fraction += round; + } + } + else + { + fraction = 0; + } + + return true; + } + + hour = default; + minute = default; + second = default; + fraction = default; + return false; + } + + private static bool ParseTimeAndWhitespace( + string rawValue, + int start, + [NotNullWhen(true)] out int? hour, + [NotNullWhen(true)] out int? minute, + [NotNullWhen(true)] out int? second, + [NotNullWhen(true)] out int? fraction) + { + if (TryParseTime(rawValue, ref start, out hour, out minute, out second, out fraction)) + { + while (start < rawValue.Length) + { + start++; + } + return start == rawValue.Length; + } + return false; + } + + private static bool TryParseTimeAndZoneAndWhitespace( + string rawValue, + int start, + [NotNullWhen(true)] out int? hour, + [NotNullWhen(true)] out int? minute, + [NotNullWhen(true)] out int? second, + [NotNullWhen(true)] out int? fraction, + [NotNullWhen(true)] out XsdDateTimeKind? kind, + [NotNullWhen(true)] out int? zoneHour, + [NotNullWhen(true)] out int? zoneMinute) + { + if (TryParseTime(rawValue, ref start, out hour, out minute, out second, out fraction)) + { + if (TryParseZoneAndWhitespace(rawValue, start, out kind, out zoneHour, out zoneMinute)) + { + return true; + } + } + + kind = default; + zoneHour = default; + zoneMinute = default; + return false; + } + + private static bool TryParseZoneAndWhitespace( + string rawValue, + int start, + [NotNullWhen(true)] out XsdDateTimeKind? kind, + [NotNullWhen(true)] out int? zoneHour, + [NotNullWhen(true)] out int? zoneMinute) + { + const int zoneHourOfUnspecified = 0; + const int zoneHourOfZulu = 0; + const int zoneMinuteOfUnspecified = 0; + const int zoneMinuteOfZulu = 0; + + if (start < rawValue.Length) + { + char ch = rawValue[start]; + if (ch == 'Z' || ch == 'z') + { + kind = XsdDateTimeKind.Zulu; + zoneHour = zoneHourOfZulu; + zoneMinute = zoneMinuteOfZulu; + start++; + } + else if (start + 5 < rawValue.Length && + ParseTwoDigits(rawValue, start + s_Lz_, out zoneHour) && + zoneHour <= 99 && + ParseChar(rawValue, start + s_lz_zz, ':') && + ParseTwoDigits(rawValue, start + s_lz_zz_, out zoneMinute) && + zoneMinute <= 99) + { + switch (ch) + { + case '-': + { + kind = XsdDateTimeKind.LocalWestOfZulu; + start += s_lz_zz_zz; + break; + } + case '+': + { + kind = XsdDateTimeKind.LocalEastOfZulu; + start += s_lz_zz_zz; + break; + } + default: + { + kind = default; + break; + } + } + } + else + { + kind = XsdDateTimeKind.Unspecified; + zoneHour = zoneHourOfUnspecified; + zoneMinute = zoneMinuteOfUnspecified; + } + } + else + { + kind = XsdDateTimeKind.Unspecified; + zoneHour = zoneHourOfUnspecified; + zoneMinute = zoneMinuteOfUnspecified; + } + + while (start < rawValue.Length && char.IsWhiteSpace(rawValue[start])) + { + start++; + } + + return start == rawValue.Length; + } + } +} diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs new file mode 100644 index 00000000000000..c82c55f47a35d6 --- /dev/null +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Xml.Schema.DateAndTime.Specifications; + +namespace System.Xml.Schema.DateAndTime.Helpers +{ + internal struct DateAndTimeInfo + { + public int Day { get; } + public int Fraction { get; } + public int Hour { get; } + public XsdDateTimeKind Kind { get; } + public int Minute { get; } + public int Month { get; } + public int Second { get; } + public DateTimeTypeCode TypeCode { get; } + public int Year { get; } + public int ZoneHour { get; } + public int ZoneMinute { get; } + + public DateAndTimeInfo( + int day, + int fraction, + int hour, + XsdDateTimeKind kind, + int minute, + int month, + int second, + DateTimeTypeCode typeCode, + int year, + int zoneHour, + int zoneMinute) + { + Day = day; + Fraction = fraction; + Hour = hour; + Kind = kind; + Minute = minute; + Month = month; + Second = second; + TypeCode = typeCode; + Year = year; + ZoneHour = zoneHour; + ZoneMinute = zoneMinute; + } + + public DateAndTimeInfo() + : this( + default, + default, + default, + XsdDateTimeKind.Unspecified, + default, + default, + default, + default, + default, + 0, + 0) + { + } + } +} diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs deleted file mode 100644 index 11e90b5a74b0f9..00000000000000 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Parsers/XsdDateTimeParser.cs +++ /dev/null @@ -1,389 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Xml.Schema.DateAndTime.Specifications; - -namespace System.Xml.Schema.DateAndTime.Parsers -{ - internal struct XsdDateTimeParser - { - /// - /// Maximum number of fraction digits. - /// - public const short MaxFractionDigits = 7; - private const int firstDay = 1; - private const int firstMonth = 1; - private const int leapYear = 1904; - - public static readonly int s_Lz_ = "-".Length; - public static readonly int s_lz_zz = "-zz".Length; - public static readonly int s_lz_zz_ = "-zz:".Length; - public static readonly int s_lz_zz_zz = "-zz:zz".Length; - public static readonly int s_lzHH = "HH".Length; - public static readonly int s_lzHH_ = "HH:".Length; - public static readonly int s_lzHH_mm = "HH:mm".Length; - public static readonly int s_lzHH_mm_ = "HH:mm:".Length; - public static readonly int s_lzHH_mm_ss = "HH:mm:ss".Length; - public static readonly int s_lzyyyy = "yyyy".Length; - public static readonly int s_lzyyyy_ = "yyyy-".Length; - public static readonly int s_lzyyyy_MM = "yyyy-MM".Length; - public static readonly int s_lzyyyy_MM_ = "yyyy-MM-".Length; - public static readonly int s_lzyyyy_MM_dd = "yyyy-MM-dd".Length; - private static readonly int s_Lz__ = "--".Length; - private static readonly int s_Lz___ = "---".Length; - private static readonly int s_lz___dd = "---dd".Length; - private static readonly int s_lz__mm = "--MM".Length; - private static readonly int s_lz__mm_ = "--MM-".Length; - private static readonly int s_lz__mm__ = "--MM--".Length; - private static readonly int s_lz__mm_dd = "--MM-dd".Length; - private static readonly int s_lzyyyy_MM_ddT = "yyyy-MM-ddT".Length; - - public int day; - public int fraction; - public int hour; - public XsdDateTimeKind kind; - public int minute; - public int month; - public int second; - public DateTimeTypeCode typeCode; - public int year; - public int zoneHour; - public int zoneMinute; - - private int _length; - private string _text; - private static ReadOnlySpan Power10 => [-1, 10, 100, 1000, 10000, 100000, 1000000]; - - public bool Parse(string text, XsdDateTimeFlags kinds) - { - const XsdDateTimeFlags dateVariants = XsdDateTimeFlags.DateTime - | XsdDateTimeFlags.Date - | XsdDateTimeFlags.XdrDateTime - | XsdDateTimeFlags.XdrDateTimeNoTz; - - _text = text; - _length = text.Length; - - // Skip leading whitespace - int start = 0; - while (start < _length && char.IsWhiteSpace(text[start])) - { - start++; - } - - // Choose format starting from the most common and trying not to reparse the same thing too many times - if (Test(kinds, dateVariants) && ParseDate(start)) - { - if (Test(kinds, XsdDateTimeFlags.DateTime) && - ParseChar(start + s_lzyyyy_MM_dd, 'T') && - ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT)) - { - typeCode = DateTimeTypeCode.DateTime; - return true; - } - - if (Test(kinds, XsdDateTimeFlags.Date) - && ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd)) - { - typeCode = DateTimeTypeCode.Date; - return true; - } - - if (Test(kinds, XsdDateTimeFlags.XdrDateTime) && - (ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd) || - (ParseChar(start + s_lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT)))) - { - typeCode = DateTimeTypeCode.XdrDateTime; - return true; - } - - if (Test(kinds, XsdDateTimeFlags.XdrDateTimeNoTz)) - { - if (ParseChar(start + s_lzyyyy_MM_dd, 'T')) - { - if (ParseTimeAndWhitespace(start + s_lzyyyy_MM_ddT)) - { - typeCode = DateTimeTypeCode.XdrDateTime; - return true; - } - } - else - { - typeCode = DateTimeTypeCode.XdrDateTime; - return true; - } - } - } - - if (Test(kinds, XsdDateTimeFlags.Time) && ParseTimeAndZoneAndWhitespace(start)) - { - year = leapYear; - month = firstMonth; - day = firstDay; - typeCode = DateTimeTypeCode.Time; - return true; - } - - if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz) && ParseTimeAndWhitespace(start)) - { - year = leapYear; - month = firstMonth; - day = firstDay; - typeCode = DateTimeTypeCode.Time; - return true; - } - - if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear) && Parse4Dig(start, ref year) && 1 <= year) - { - if (Test(kinds, XsdDateTimeFlags.GYearMonth) && - ParseChar(start + s_lzyyyy, '-') && - Parse2Dig(start + s_lzyyyy_, ref month) && 1 <= month && month <= 12 && - ParseZoneAndWhitespace(start + s_lzyyyy_MM)) - { - day = firstDay; - typeCode = DateTimeTypeCode.GYearMonth; - return true; - } - - if (Test(kinds, XsdDateTimeFlags.GYear) && ParseZoneAndWhitespace(start + s_lzyyyy)) - { - month = firstMonth; - day = firstDay; - typeCode = DateTimeTypeCode.GYear; - return true; - } - } - - if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth) && - ParseChar(start, '-') && - ParseChar(start + s_Lz_, '-') && - Parse2Dig(start + s_Lz__, ref month) && 1 <= month && month <= 12) - { - if (Test(kinds, XsdDateTimeFlags.GMonthDay) && - ParseChar(start + s_lz__mm, '-') && - Parse2Dig(start + s_lz__mm_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month) && - ParseZoneAndWhitespace(start + s_lz__mm_dd)) - { - year = leapYear; - typeCode = DateTimeTypeCode.GMonthDay; - return true; - } - - if (Test(kinds, XsdDateTimeFlags.GMonth) && - (ParseZoneAndWhitespace(start + s_lz__mm) || - (ParseChar(start + s_lz__mm, '-') && - ParseChar(start + s_lz__mm_, '-') && - ParseZoneAndWhitespace(start + s_lz__mm__)))) - { - year = leapYear; - day = firstDay; - typeCode = DateTimeTypeCode.GMonth; - return true; - } - } - - if (Test(kinds, XsdDateTimeFlags.GDay) && - ParseChar(start, '-') && - ParseChar(start + s_Lz_, '-') && - ParseChar(start + s_Lz__, '-') && - Parse2Dig(start + s_Lz___, ref day) && - 1 <= day && - day <= DateTime.DaysInMonth(leapYear, firstMonth) && - ParseZoneAndWhitespace(start + s_lz___dd)) - { - year = leapYear; - month = firstMonth; - typeCode = DateTimeTypeCode.GDay; - return true; - } - - return false; - } - - private static bool Test(XsdDateTimeFlags left, XsdDateTimeFlags right) - { - return (left & right) != 0; - } - - private bool Parse2Dig(int start, ref int num) - { - if (start + 1 < _length) - { - int d2 = _text[start] - '0'; - int d1 = _text[start + 1] - '0'; - if (0 <= d2 && d2 < 10 && - 0 <= d1 && d1 < 10 - ) - { - num = d2 * 10 + d1; - return true; - } - } - return false; - } - - private bool Parse4Dig(int start, ref int num) - { - if (start + 3 < _length) - { - int d4 = _text[start] - '0'; - int d3 = _text[start + 1] - '0'; - int d2 = _text[start + 2] - '0'; - int d1 = _text[start + 3] - '0'; - if (0 <= d4 && d4 < 10 && - 0 <= d3 && d3 < 10 && - 0 <= d2 && d2 < 10 && - 0 <= d1 && d1 < 10 - ) - { - num = ((d4 * 10 + d3) * 10 + d2) * 10 + d1; - return true; - } - } - return false; - } - - private bool ParseChar(int start, char ch) - { - return start < _length && _text[start] == ch; - } - - private bool ParseDate(int start) - { - return - Parse4Dig(start, ref year) && 1 <= year && - ParseChar(start + s_lzyyyy, '-') && - Parse2Dig(start + s_lzyyyy_, ref month) && 1 <= month && month <= 12 && - ParseChar(start + s_lzyyyy_MM, '-') && - Parse2Dig(start + s_lzyyyy_MM_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(year, month); - } - - private bool ParseTime(ref int start) - { - if ( - Parse2Dig(start, ref hour) && hour < 24 && - ParseChar(start + s_lzHH, ':') && - Parse2Dig(start + s_lzHH_, ref minute) && minute < 60 && - ParseChar(start + s_lzHH_mm, ':') && - Parse2Dig(start + s_lzHH_mm_, ref second) && second < 60 - ) - { - start += s_lzHH_mm_ss; - if (ParseChar(start, '.')) - { - // Parse factional part of seconds - // We allow any number of digits, but keep only first 7 - this.fraction = 0; - int fractionDigits = 0; - int round = 0; - while (++start < _length) - { - int d = _text[start] - '0'; - if (9u < unchecked((uint)d)) - { // d < 0 || 9 < d - break; - } - if (fractionDigits < MaxFractionDigits) - { - this.fraction = (this.fraction * 10) + d; - } - else if (fractionDigits == MaxFractionDigits) - { - if (5 < d) - { - round = 1; - } - else if (d == 5) - { - round = -1; - } - } - else if (round < 0 && d != 0) - { - round = 1; - } - fractionDigits++; - } - if (fractionDigits < MaxFractionDigits) - { - if (fractionDigits == 0) - { - return false; // cannot end with . - } - fraction *= Power10[MaxFractionDigits - fractionDigits]; - } - else - { - if (round < 0) - { - round = fraction & 1; - } - fraction += round; - } - } - return true; - } - // cleanup - conflict with gYear - hour = 0; - return false; - } - - private bool ParseTimeAndWhitespace(int start) - { - if (ParseTime(ref start)) - { - while (start < _length) - { - start++; - } - return start == _length; - } - return false; - } - - private bool ParseTimeAndZoneAndWhitespace(int start) - { - if (ParseTime(ref start) && ParseZoneAndWhitespace(start)) - { - return true; - } - return false; - } - - private bool ParseZoneAndWhitespace(int start) - { - if (start < _length) - { - char ch = _text[start]; - if (ch == 'Z' || ch == 'z') - { - kind = XsdDateTimeKind.Zulu; - start++; - } - else if ((start + 5 < _length) && - Parse2Dig(start + s_Lz_, ref zoneHour) && - zoneHour <= 99 && - ParseChar(start + s_lz_zz, ':') && - Parse2Dig(start + s_lz_zz_, ref zoneMinute) && - zoneMinute <= 99) - { - if (ch == '-') - { - kind = XsdDateTimeKind.LocalWestOfZulu; - start += s_lz_zz_zz; - } - else if (ch == '+') - { - kind = XsdDateTimeKind.LocalEastOfZulu; - start += s_lz_zz_zz; - } - } - } - while (start < _length && char.IsWhiteSpace(_text[start])) - { - start++; - } - return start == _length; - } - } -} diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs index faf38890dd447d..8f419bef9203d2 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs @@ -4,7 +4,8 @@ using System.Diagnostics; using System.Numerics; using System.Text; -using System.Xml.Schema.DateAndTime.Parsers; +using System.Xml.Schema.DateAndTime.Converters; +using System.Xml.Schema.DateAndTime.Helpers; using System.Xml.Schema.DateAndTime.Specifications; namespace System.Xml.Schema @@ -78,38 +79,38 @@ internal struct XsdDateTime /// public XsdDateTime(string text, XsdDateTimeFlags kinds) : this() { - XsdDateTimeParser parser = default; - if (!parser.Parse(text, kinds)) + if (!DateAndTimeConverter.TryParse(text, kinds, out DateAndTimeInfo parsedValue)) { throw new FormatException(SR.Format(SR.XmlConvert_BadFormat, text, kinds)); } - InitiateXsdDateTime(parser); + + InitiateXsdDateTime(parsedValue); } - private XsdDateTime(XsdDateTimeParser parser) : this() + private XsdDateTime(DateAndTimeInfo parsedValue) : this() { - InitiateXsdDateTime(parser); + InitiateXsdDateTime(parsedValue); } - private void InitiateXsdDateTime(XsdDateTimeParser parser) + private void InitiateXsdDateTime(DateAndTimeInfo parsedValue) { - _dt = new DateTime(parser.year, parser.month, parser.day, parser.hour, parser.minute, parser.second); - if (parser.fraction != 0) + _dt = new DateTime(parsedValue.Year, parsedValue.Month, parsedValue.Day, parsedValue.Hour, parsedValue.Minute, parsedValue.Second); + if (parsedValue.Fraction != 0) { - _dt = _dt.AddTicks(parser.fraction); + _dt = _dt.AddTicks(parsedValue.Fraction); } - _extra = (uint)(((int)parser.typeCode << TypeShift) | ((int)parser.kind << KindShift) | (parser.zoneHour << ZoneHourShift) | parser.zoneMinute); + _extra = (uint)(((int)parsedValue.TypeCode << TypeShift) | ((int)parsedValue.Kind << KindShift) | (parsedValue.ZoneHour << ZoneHourShift) | parsedValue.ZoneMinute); } internal static bool TryParse(string text, XsdDateTimeFlags kinds, out XsdDateTime result) { - XsdDateTimeParser parser = default; - if (!parser.Parse(text, kinds)) + if (!DateAndTimeConverter.TryParse(text, kinds, out DateAndTimeInfo parsedValue)) { result = default; return false; } - result = new XsdDateTime(parser); + + result = new XsdDateTime(parsedValue); return true; } @@ -494,14 +495,14 @@ public bool TryFormat(Span destination, out int charsWritten) // Serialize year, month and day private void PrintDate(ref ValueStringBuilder vsb) { - Span text = vsb.AppendSpan(XsdDateTimeParser.s_lzyyyy_MM_dd); + Span text = vsb.AppendSpan(DateAndTimeConverter.s_lzyyyy_MM_dd); int year, month, day; GetYearMonthDay(out year, out month, out day); WriteXDigits(text, 0, year, 4); - text[XsdDateTimeParser.s_lzyyyy] = '-'; - Write2Digits(text, XsdDateTimeParser.s_lzyyyy_, month); - text[XsdDateTimeParser.s_lzyyyy_MM] = '-'; - Write2Digits(text, XsdDateTimeParser.s_lzyyyy_MM_, day); + text[DateAndTimeConverter.s_lzyyyy] = '-'; + Write2Digits(text, DateAndTimeConverter.s_lzyyyy_, month); + text[DateAndTimeConverter.s_lzyyyy_MM] = '-'; + Write2Digits(text, DateAndTimeConverter.s_lzyyyy_MM_, day); } // When printing the date, we need the year, month and the day. When @@ -560,16 +561,16 @@ private void GetYearMonthDay(out int year, out int month, out int day) // Serialize hour, minute, second and fraction private void PrintTime(ref ValueStringBuilder vsb) { - Span text = vsb.AppendSpan(XsdDateTimeParser.s_lzHH_mm_ss); + Span text = vsb.AppendSpan(DateAndTimeConverter.s_lzHH_mm_ss); Write2Digits(text, 0, Hour); - text[XsdDateTimeParser.s_lzHH] = ':'; - Write2Digits(text, XsdDateTimeParser.s_lzHH_, Minute); - text[XsdDateTimeParser.s_lzHH_mm] = ':'; - Write2Digits(text, XsdDateTimeParser.s_lzHH_mm_, Second); + text[DateAndTimeConverter.s_lzHH] = ':'; + Write2Digits(text, DateAndTimeConverter.s_lzHH_, Minute); + text[DateAndTimeConverter.s_lzHH_mm] = ':'; + Write2Digits(text, DateAndTimeConverter.s_lzHH_mm_, Second); int fraction = Fraction; if (fraction != 0) { - int fractionDigits = XsdDateTimeParser.MaxFractionDigits; + int fractionDigits = DateAndTimeConverter.MaxFractionDigits; while (fraction % 10 == 0) { fractionDigits--; @@ -592,18 +593,18 @@ private void PrintZone(ref ValueStringBuilder vsb) vsb.Append('Z'); break; case XsdDateTimeKind.LocalWestOfZulu: - text = vsb.AppendSpan(XsdDateTimeParser.s_lz_zz_zz); + text = vsb.AppendSpan(DateAndTimeConverter.s_lz_zz_zz); text[0] = '-'; - Write2Digits(text, XsdDateTimeParser.s_Lz_, ZoneHour); - text[XsdDateTimeParser.s_lz_zz] = ':'; - Write2Digits(text, XsdDateTimeParser.s_lz_zz_, ZoneMinute); + Write2Digits(text, DateAndTimeConverter.s_Lz_, ZoneHour); + text[DateAndTimeConverter.s_lz_zz] = ':'; + Write2Digits(text, DateAndTimeConverter.s_lz_zz_, ZoneMinute); break; case XsdDateTimeKind.LocalEastOfZulu: - text = vsb.AppendSpan(XsdDateTimeParser.s_lz_zz_zz); + text = vsb.AppendSpan(DateAndTimeConverter.s_lz_zz_zz); text[0] = '+'; - Write2Digits(text, XsdDateTimeParser.s_Lz_, ZoneHour); - text[XsdDateTimeParser.s_lz_zz] = ':'; - Write2Digits(text, XsdDateTimeParser.s_lz_zz_, ZoneMinute); + Write2Digits(text, DateAndTimeConverter.s_Lz_, ZoneHour); + text[DateAndTimeConverter.s_lz_zz] = ':'; + Write2Digits(text, DateAndTimeConverter.s_lz_zz_, ZoneMinute); break; default: // do nothing From 7a30df29b886966b06e920c3a3ba1bef4f48e9f0 Mon Sep 17 00:00:00 2001 From: Maksim Golev Date: Wed, 17 Sep 2025 13:37:38 +0400 Subject: [PATCH 09/10] fix(#93189): Fix functionality of "ms:format-date" function. --- .../src/System.Private.Xml.csproj | 2 + .../Converters/DateAndTimeConverter.cs | 124 ++++++++++++------ .../DateAndTime/Helpers/DateAndTimeInfo.cs | 14 +- .../Schema/DateAndTime/Helpers/DateInfo.cs | 25 ++++ .../System/Xml/Schema/DateAndTime/XsdDate.cs | 37 ++++++ .../src/System/Xml/Schema/XsdDateTime.cs | 2 +- .../System/Xml/Xsl/Runtime/XsltFunctions.cs | 28 +++- .../src/System/Xml/Xsl/Runtime/XsltLibrary.cs | 2 + .../System/Xml/Xsl/Xslt/QilGeneratorEnv.cs | 17 ++- .../src/System/Xml/Xsl/Xslt/XsltQilFactory.cs | 10 ++ .../TestData/XsltApiV2/baseline/bug93189.xml | 2 +- .../TestFiles/TestData/XsltApiV2/bug93189.xsl | 6 +- 12 files changed, 210 insertions(+), 59 deletions(-) create mode 100644 src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateInfo.cs create mode 100644 src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdDate.cs diff --git a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj index de69de4b2f81f8..05a1583f55686a 100644 --- a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj +++ b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj @@ -44,8 +44,10 @@ + + TextTemplatingFileGenerator HtmlEncodedRawTextWriter.cs diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs index 281d8b995c9a0d..5a15e892b227b1 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs @@ -13,9 +13,6 @@ internal static class DateAndTimeConverter /// Maximum number of fraction digits. /// public const short MaxFractionDigits = 7; - private const int firstDay = 1; - private const int firstMonth = 1; - private const int leapYear = 1904; public static readonly int s_Lz_ = "-".Length; public static readonly int s_lz_zz = "-zz".Length; @@ -51,6 +48,11 @@ public static bool TryParse(string text, XsdDateTimeFlags kinds, out DateAndTime start++; } + if (TryParseAsDateTime(text, kinds, start, out parsedValue)) + { + return true; + } + if (TryParseAsDate(text, kinds, start, out parsedValue)) { return true; @@ -84,35 +86,76 @@ public static bool TryParse(string text, XsdDateTimeFlags kinds, out DateAndTime return false; } + public static bool TryParse(string text, out DateInfo parsedValue) + { + int start = 0; + while (start < text.Length && char.IsWhiteSpace(text[start])) + { + start++; + } + + if (TryParseAsDate(text, XsdDateTimeFlags.Date, start, out DateAndTimeInfo rawParsedValue)) + { + parsedValue = rawParsedValue.Date; + return true; + } + + parsedValue = default; + return false; + } + private static bool TryParseAsDate( string text, XsdDateTimeFlags kinds, int start, out DateAndTimeInfo parsedValue) { - const XsdDateTimeFlags dateVariants = XsdDateTimeFlags.DateTime - | XsdDateTimeFlags.Date + DateInfo date; + int? zoneHour, zoneMinute; + XsdDateTimeKind? kind; + + if (Test(kinds, XsdDateTimeFlags.Date) && TryParseDate(text, start, out date)) + { + if (TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM_dd, out kind, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(date, 0, 0, kind.Value, 0, 0, DateTimeTypeCode.Date, zoneHour.Value, zoneMinute.Value); + return true; + } + + if (ParseChar(text, start + s_lzyyyy_MM_dd, 'T') + && TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out _, out _, out _, out _, out kind, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(date, 0, 0, kind.Value, 0, 0, DateTimeTypeCode.Date, zoneHour.Value, zoneMinute.Value); + return true; + } + } + + parsedValue = default; + return false; + } + + private static bool TryParseAsDateTime( + string text, + XsdDateTimeFlags kinds, + int start, + out DateAndTimeInfo parsedValue) + { + const XsdDateTimeFlags dateTimeVariants = XsdDateTimeFlags.DateTime | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrDateTimeNoTz; - int? year, month, day, hour, minute, second, fraction, zoneHour, zoneMinute; + DateInfo date; + int? hour, minute, second, fraction, zoneHour, zoneMinute; XsdDateTimeKind? kind; // Choose format starting from the most common and trying not to reparse the same thing too many times - if (Test(kinds, dateVariants) && TryParseDate(text, start, out year, out month, out day)) + if (Test(kinds, dateTimeVariants) && TryParseDate(text, start, out date)) { if (Test(kinds, XsdDateTimeFlags.DateTime) && ParseChar(text, start + s_lzyyyy_MM_dd, 'T') && TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out hour, out minute, out second, out fraction, out kind, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(day.Value, fraction.Value, hour.Value, kind.Value, minute.Value, month.Value, second.Value, DateTimeTypeCode.DateTime, year.Value, zoneHour.Value, zoneMinute.Value); - return true; - } - - if (Test(kinds, XsdDateTimeFlags.Date) - && TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM_dd, out kind, out zoneHour, out zoneMinute)) - { - parsedValue = new DateAndTimeInfo(day.Value, 0, 0, kind.Value, 0, month.Value, 0, DateTimeTypeCode.Date, year.Value, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, fraction.Value, hour.Value, kind.Value, minute.Value, second.Value, DateTimeTypeCode.DateTime, zoneHour.Value, zoneMinute.Value); return true; } @@ -120,14 +163,14 @@ private static bool TryParseAsDate( { if (TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM_dd, out kind, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(day.Value, 0, 0, kind.Value, 0, month.Value, 0, DateTimeTypeCode.XdrDateTime, year.Value, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, 0, 0, kind.Value, 0, 0, DateTimeTypeCode.XdrDateTime, zoneHour.Value, zoneMinute.Value); return true; } if (ParseChar(text, start + s_lzyyyy_MM_dd, 'T') && TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out hour, out minute, out second, out fraction, out kind, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(day.Value, fraction.Value, hour.Value, kind.Value, minute.Value, month.Value, second.Value, DateTimeTypeCode.XdrDateTime, year.Value, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, fraction.Value, hour.Value, kind.Value, minute.Value, second.Value, DateTimeTypeCode.XdrDateTime, zoneHour.Value, zoneMinute.Value); return true; } } @@ -138,13 +181,13 @@ private static bool TryParseAsDate( { if (ParseTimeAndWhitespace(text, start + s_lzyyyy_MM_ddT, out hour, out minute, out second, out fraction)) { - parsedValue = new DateAndTimeInfo(day.Value, fraction.Value, hour.Value, XsdDateTimeKind.Unspecified, minute.Value, month.Value, second.Value, DateTimeTypeCode.XdrDateTime, year.Value, 0, 0); + parsedValue = new DateAndTimeInfo(date, fraction.Value, hour.Value, XsdDateTimeKind.Unspecified, minute.Value, second.Value, DateTimeTypeCode.XdrDateTime, 0, 0); return true; } } else { - parsedValue = new DateAndTimeInfo(day.Value, 0, 0, XsdDateTimeKind.Unspecified, 0, month.Value, 0, DateTimeTypeCode.XdrDateTime, year.Value, 0, 0); + parsedValue = new DateAndTimeInfo(date, 0, 0, XsdDateTimeKind.Unspecified, 0, 0, DateTimeTypeCode.XdrDateTime, 0, 0); return true; } } @@ -162,7 +205,7 @@ private static bool TryParseAsTime( { if (Test(kinds, XsdDateTimeFlags.Time) && TryParseTimeAndZoneAndWhitespace(text, start, out int? hour, out int? minute, out int? second, out int? fraction, out XsdDateTimeKind? kind, out int? zoneHour, out int? zoneMinute)) { - parsedValue = new DateAndTimeInfo(firstDay, fraction.Value, hour.Value, kind.Value, minute.Value, firstMonth, second.Value, DateTimeTypeCode.Time, leapYear, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(DateInfo.DefaultValue, fraction.Value, hour.Value, kind.Value, minute.Value, second.Value, DateTimeTypeCode.Time, zoneHour.Value, zoneMinute.Value); return true; } @@ -178,7 +221,7 @@ private static bool TryParseAsXdrTimeNoTz( { if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz) && ParseTimeAndWhitespace(text, start, out int? hour, out int? minute, out int? second, out int? fraction)) { - parsedValue = new DateAndTimeInfo(firstDay, fraction.Value, hour.Value, XsdDateTimeKind.Unspecified, minute.Value, firstMonth, second.Value, DateTimeTypeCode.Time, leapYear, default, default); + parsedValue = new DateAndTimeInfo(DateInfo.DefaultValue, fraction.Value, hour.Value, XsdDateTimeKind.Unspecified, minute.Value, second.Value, DateTimeTypeCode.Time, default, default); return true; } @@ -192,7 +235,8 @@ private static bool TryParseAsGYearOrGYearMonth( int start, out DateAndTimeInfo parsedValue) { - int? year, month, zoneHour, zoneMinute; + int? month, year, zoneHour, zoneMinute; + DateInfo date; XsdDateTimeKind? kind; if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear) && ParseFourDigits(text, start, out year) && 1 <= year) @@ -204,18 +248,20 @@ private static bool TryParseAsGYearOrGYearMonth( month <= 12 && TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM, out kind, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(firstDay, default, default, kind.Value, default, month.Value, default, DateTimeTypeCode.GYearMonth, year.Value, zoneHour.Value, zoneMinute.Value); + date = new DateInfo(DateInfo.FirstDay, month.Value, year.Value); + parsedValue = new DateAndTimeInfo(date, default, default, kind.Value, default, default, DateTimeTypeCode.GYearMonth, zoneHour.Value, zoneMinute.Value); return true; } if (Test(kinds, XsdDateTimeFlags.GYear) && TryParseZoneAndWhitespace(text, start + s_lzyyyy, out kind, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(firstDay, default, default, kind.Value, default, firstMonth, default, DateTimeTypeCode.GYear, year.Value, zoneHour.Value, zoneMinute.Value); + date = new DateInfo(DateInfo.FirstDay, DateInfo.FirstMonth, year.Value); + parsedValue = new DateAndTimeInfo(date, default, default, kind.Value, default, default, DateTimeTypeCode.GYear, zoneHour.Value, zoneMinute.Value); return true; } } - parsedValue = new DateAndTimeInfo(default, default, default, XsdDateTimeKind.Unspecified, default, default, default, default, default, default, default); + parsedValue = new DateAndTimeInfo(default, default, default, XsdDateTimeKind.Unspecified, default, default, default, default, default); return false; } @@ -225,7 +271,8 @@ private static bool TryParseAsGMonthOrGMonthDay( int start, out DateAndTimeInfo parsedValue) { - int? month, day, zoneHour, zoneMinute; + int? day, month, zoneHour, zoneMinute; + DateInfo date; XsdDateTimeKind? kind; if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth) && @@ -235,10 +282,11 @@ private static bool TryParseAsGMonthOrGMonthDay( { if (Test(kinds, XsdDateTimeFlags.GMonthDay) && ParseChar(text, start + s_lz__mm, '-') && - ParseTwoDigits(text, start + s_lz__mm_, out day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month.Value) && + ParseTwoDigits(text, start + s_lz__mm_, out day) && 1 <= day && day <= DateTime.DaysInMonth(DateInfo.LeapYear, month.Value) && TryParseZoneAndWhitespace(text, start + s_lz__mm_dd, out kind, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(day.Value, default, default, kind.Value, default, month.Value, default, DateTimeTypeCode.GMonthDay, leapYear, zoneHour.Value, zoneMinute.Value); + date = new DateInfo(day.Value, month.Value, DateInfo.LeapYear); + parsedValue = new DateAndTimeInfo(date, default, default, kind.Value, default, default, DateTimeTypeCode.GMonthDay, zoneHour.Value, zoneMinute.Value); return true; } @@ -248,7 +296,8 @@ private static bool TryParseAsGMonthOrGMonthDay( ParseChar(text, start + s_lz__mm_, '-') && TryParseZoneAndWhitespace(text, start + s_lz__mm__, out kind, out zoneHour, out zoneMinute))) { - parsedValue = new DateAndTimeInfo(firstDay, default, default, kind.Value, default, month.Value, default, DateTimeTypeCode.GMonth, leapYear, zoneHour.Value, zoneMinute.Value); + date = new DateInfo(DateInfo.FirstDay, month.Value, DateInfo.LeapYear); + parsedValue = new DateAndTimeInfo(date, default, default, kind.Value, default, default, DateTimeTypeCode.GMonth, zoneHour.Value, zoneMinute.Value); return true; } } @@ -264,6 +313,7 @@ private static bool TryParseAsGDay( out DateAndTimeInfo parsedValue) { int? day, zoneHour, zoneMinute; + DateInfo date; XsdDateTimeKind? kind; if (Test(kinds, XsdDateTimeFlags.GDay) && @@ -272,10 +322,11 @@ private static bool TryParseAsGDay( ParseChar(text, start + s_Lz__, '-') && ParseTwoDigits(text, start + s_Lz___, out day) && 1 <= day && - day <= DateTime.DaysInMonth(leapYear, firstMonth) && + day <= DateTime.DaysInMonth(DateInfo.LeapYear, DateInfo.FirstMonth) && TryParseZoneAndWhitespace(text, start + s_lz___dd, out kind, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(day.Value, default, default, kind.Value, default, firstMonth, default, DateTimeTypeCode.GDay, leapYear, zoneHour.Value, zoneMinute.Value); + date = new DateInfo(day.Value, DateInfo.FirstMonth, DateInfo.LeapYear); + parsedValue = new DateAndTimeInfo(date, default, default, kind.Value, default, default, DateTimeTypeCode.GDay, zoneHour.Value, zoneMinute.Value); return true; } @@ -345,10 +396,10 @@ private static bool Test(XsdDateTimeFlags left, XsdDateTimeFlags right) private static bool TryParseDate( string rawValue, int start, - [NotNullWhen(true)] out int? year, - [NotNullWhen(true)] out int? month, - [NotNullWhen(true)] out int? day) + out DateInfo parsedDate) { + int? day, month, year; + if (ParseFourDigits(rawValue, start, out year) && 1 <= year && ParseChar(rawValue, start + s_lzyyyy, '-') && @@ -360,12 +411,11 @@ private static bool TryParseDate( day <= DateTime.DaysInMonth(year.Value, month.Value) ) { + parsedDate = new DateInfo(day.Value, month.Value, year.Value); return true; } - year = default; - month = default; - day = default; + parsedDate = default; return false; } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs index c82c55f47a35d6..a29ad46b78e809 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs @@ -7,40 +7,34 @@ namespace System.Xml.Schema.DateAndTime.Helpers { internal struct DateAndTimeInfo { - public int Day { get; } + public DateInfo Date { get; } public int Fraction { get; } public int Hour { get; } public XsdDateTimeKind Kind { get; } public int Minute { get; } - public int Month { get; } public int Second { get; } public DateTimeTypeCode TypeCode { get; } - public int Year { get; } public int ZoneHour { get; } public int ZoneMinute { get; } public DateAndTimeInfo( - int day, + DateInfo date, int fraction, int hour, XsdDateTimeKind kind, int minute, - int month, int second, DateTimeTypeCode typeCode, - int year, int zoneHour, int zoneMinute) { - Day = day; + Date = date; Fraction = fraction; Hour = hour; Kind = kind; Minute = minute; - Month = month; Second = second; TypeCode = typeCode; - Year = year; ZoneHour = zoneHour; ZoneMinute = zoneMinute; } @@ -54,8 +48,6 @@ public DateAndTimeInfo() default, default, default, - default, - default, 0, 0) { diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateInfo.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateInfo.cs new file mode 100644 index 00000000000000..c238d3a31af727 --- /dev/null +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateInfo.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Xml.Schema.DateAndTime.Helpers +{ + internal struct DateInfo + { + public static readonly DateInfo DefaultValue = new DateInfo(FirstDay, FirstMonth, LeapYear); + + public const int FirstDay = 1; + public const int FirstMonth = 1; + public const int LeapYear = 1904; + + public int Day { get; } + public int Month { get; } + public int Year { get; } + + public DateInfo(int day, int month, int year) + { + Day = day; + Month = month; + Year = year; + } + } +} diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdDate.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdDate.cs new file mode 100644 index 00000000000000..aa0daada57a811 --- /dev/null +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdDate.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Xml.Schema.DateAndTime.Converters; +using System.Xml.Schema.DateAndTime.Helpers; + +namespace System.Xml.Schema.DateAndTime +{ + internal struct XsdDate : IFormattable + { + private DateOnly Date { get; set; } + + private XsdDate(DateInfo parsedValue) + : this() + { + Date = new DateOnly(parsedValue.Year, parsedValue.Month, parsedValue.Day); + } + + /// + public string ToString(string? format, IFormatProvider? formatProvider) + { + return Date.ToString(format, formatProvider); + } + + internal static bool TryParse(string text, out XsdDate result) + { + if (!DateAndTimeConverter.TryParse(text, out DateInfo parsedValue)) + { + result = default; + return false; + } + + result = new XsdDate(parsedValue); + return true; + } + } +} diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs index 8f419bef9203d2..62bbf4ac542dab 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs @@ -94,7 +94,7 @@ private XsdDateTime(DateAndTimeInfo parsedValue) : this() private void InitiateXsdDateTime(DateAndTimeInfo parsedValue) { - _dt = new DateTime(parsedValue.Year, parsedValue.Month, parsedValue.Day, parsedValue.Hour, parsedValue.Minute, parsedValue.Second); + _dt = new DateTime(parsedValue.Date.Year, parsedValue.Date.Month, parsedValue.Date.Day, parsedValue.Hour, parsedValue.Minute, parsedValue.Second); if (parsedValue.Fraction != 0) { _dt = _dt.AddTicks(parsedValue.Fraction); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs index 7d8613a4d7d3cd..d4cf9623ce5da3 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs @@ -13,6 +13,7 @@ using System.Runtime.Versioning; using System.Text; using System.Xml.Schema; +using System.Xml.Schema.DateAndTime; using System.Xml.XPath; using System.Xml.Xsl.Xslt; @@ -309,6 +310,30 @@ public static string EXslObjectType(IList value) // Msxml Extension Functions //------------------------------------------------ + /// + /// Format xsd:date as a date string for a given language using a given format string. + /// + /// Lexical representation of xsd:date. + /// Format string. + /// Specifies a culture used for formatting. + /// formatted as a date string. + public static string MSFormatDate(string date, string format, string lang) + { + try + { + if (!XsdDate.TryParse(date, out XsdDate xsdDate)) + { + return string.Empty; + } + + return xsdDate.ToString(format.Length != 0 ? format : null, GetCultureInfo(lang)); + } + catch (ArgumentException) + { + return string.Empty; + } + } + public static double MSNumber(IList value) { XsltLibrary.CheckXsltValue(value); @@ -351,7 +376,6 @@ public static double MSNumber(IList value) return d; } - // string ms:format-date(string datetime[, string format[, string language]]) // string ms:format-time(string datetime[, string format[, string language]]) // // Format xsd:dateTime as a date/time string for a given language using a given format string. @@ -379,7 +403,7 @@ public static string MSFormatDateTime(string dateTime, string format, string lan return dt.ToString(format.Length != 0 ? format : null, new CultureInfo(locale)); } catch (ArgumentException) - { // Operations with DateTime can throw this exception eventualy + { // Operations with DateTime can throw this exception eventually return string.Empty; } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs index 07e6f7585ff94b..20c91b075c2678 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs @@ -47,6 +47,8 @@ internal static class XsltMethods public static readonly MethodInfo OnCurrentNodeChanged = typeof(XmlQueryRuntime).GetMethod("OnCurrentNodeChanged")!; // MSXML extension functions + public static readonly MethodInfo MSFormatDate = typeof(XsltFunctions) + .GetMethod(nameof(XsltFunctions.MSFormatDate))!; public static readonly MethodInfo MSFormatDateTime = typeof(XsltFunctions).GetMethod("MSFormatDateTime")!; public static readonly MethodInfo MSStringCompare = typeof(XsltFunctions).GetMethod("MSStringCompare")!; public static readonly MethodInfo MSUtc = typeof(XsltFunctions).GetMethod("MSUtc")!; diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs index f0410142b8ca5a..9b1402327407ce 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs @@ -166,14 +166,23 @@ QilNode IXPathEnvironment.ResolveFunction(string prefix, string name, IList Monday, 15 June 2009 06/15/2009 - 06/15/2009 13:45:30 + 06/15/2009 6/15/2009 lundi 15 juin 2009 15/06/2009 diff --git a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl index 0b39386a32ef94..14b033f9bcf2d7 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl +++ b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl @@ -37,13 +37,13 @@ - + - + - + From 6bdbe81e54776cbc1bb8f47f695ecebcedfb0686 Mon Sep 17 00:00:00 2001 From: Maksim Golev Date: Wed, 17 Sep 2025 16:12:43 +0400 Subject: [PATCH 10/10] fix(#93189): Fix functionality of "ms:format-time" function. --- .../src/System.Private.Xml.csproj | 2 + .../Converters/DateAndTimeConverter.cs | 102 +++++++++++------- .../DateAndTime/Helpers/DateAndTimeInfo.cs | 18 +--- .../Schema/DateAndTime/Helpers/TimeInfo.cs | 37 +++++++ .../System/Xml/Schema/DateAndTime/XsdTime.cs | 42 ++++++++ .../src/System/Xml/Schema/XsdDateTime.cs | 6 +- .../System/Xml/Xsl/Runtime/XsltFunctions.cs | 24 +++++ .../src/System/Xml/Xsl/Runtime/XsltLibrary.cs | 2 + .../System/Xml/Xsl/Xslt/QilGeneratorEnv.cs | 5 +- .../src/System/Xml/Xsl/Xslt/XsltQilFactory.cs | 10 ++ .../TestData/XsltApiV2/baseline/bug93189.xml | 2 +- 11 files changed, 188 insertions(+), 62 deletions(-) create mode 100644 src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/TimeInfo.cs create mode 100644 src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdTime.cs diff --git a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj index 05a1583f55686a..0dd5b4be0cc5e0 100644 --- a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj +++ b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj @@ -45,9 +45,11 @@ + + TextTemplatingFileGenerator HtmlEncodedRawTextWriter.cs diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs index 5a15e892b227b1..102df6e65b1a53 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs @@ -104,6 +104,31 @@ public static bool TryParse(string text, out DateInfo parsedValue) return false; } + public static bool TryParse(string text, out TimeInfo parsedValue) + { + const int start = 0; + + DateAndTimeInfo rawParsedValue; + string normalizedText = text.Trim(); + + if (TryParseAsTime(normalizedText, XsdDateTimeFlags.Time, start, out rawParsedValue)) + { + parsedValue = rawParsedValue.Time; + return true; + } + else + { + if (TryParseAsDateTime(normalizedText, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.XdrDateTime, start, out rawParsedValue)) + { + parsedValue = rawParsedValue.Time; + return true; + } + } + + parsedValue = default; + return false; + } + private static bool TryParseAsDate( string text, XsdDateTimeFlags kinds, @@ -118,14 +143,14 @@ private static bool TryParseAsDate( { if (TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM_dd, out kind, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(date, 0, 0, kind.Value, 0, 0, DateTimeTypeCode.Date, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.Date, zoneHour.Value, zoneMinute.Value); return true; } if (ParseChar(text, start + s_lzyyyy_MM_dd, 'T') - && TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out _, out _, out _, out _, out kind, out zoneHour, out zoneMinute)) + && TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out kind, out _, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(date, 0, 0, kind.Value, 0, 0, DateTimeTypeCode.Date, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.Date, zoneHour.Value, zoneMinute.Value); return true; } } @@ -145,7 +170,8 @@ private static bool TryParseAsDateTime( | XsdDateTimeFlags.XdrDateTimeNoTz; DateInfo date; - int? hour, minute, second, fraction, zoneHour, zoneMinute; + TimeInfo time; + int? zoneHour, zoneMinute; XsdDateTimeKind? kind; // Choose format starting from the most common and trying not to reparse the same thing too many times @@ -153,9 +179,9 @@ private static bool TryParseAsDateTime( { if (Test(kinds, XsdDateTimeFlags.DateTime) && ParseChar(text, start + s_lzyyyy_MM_dd, 'T') && - TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out hour, out minute, out second, out fraction, out kind, out zoneHour, out zoneMinute)) + TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out kind, out time, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(date, fraction.Value, hour.Value, kind.Value, minute.Value, second.Value, DateTimeTypeCode.DateTime, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, kind.Value, time, DateTimeTypeCode.DateTime, zoneHour.Value, zoneMinute.Value); return true; } @@ -163,14 +189,15 @@ private static bool TryParseAsDateTime( { if (TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM_dd, out kind, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(date, 0, 0, kind.Value, 0, 0, DateTimeTypeCode.XdrDateTime, zoneHour.Value, zoneMinute.Value); + time = default; + parsedValue = new DateAndTimeInfo(date, kind.Value, time, DateTimeTypeCode.XdrDateTime, zoneHour.Value, zoneMinute.Value); return true; } if (ParseChar(text, start + s_lzyyyy_MM_dd, 'T') - && TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out hour, out minute, out second, out fraction, out kind, out zoneHour, out zoneMinute)) + && TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out kind, out time, out zoneHour, out zoneMinute)) { - parsedValue = new DateAndTimeInfo(date, fraction.Value, hour.Value, kind.Value, minute.Value, second.Value, DateTimeTypeCode.XdrDateTime, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, kind.Value, time, DateTimeTypeCode.XdrDateTime, zoneHour.Value, zoneMinute.Value); return true; } } @@ -179,15 +206,16 @@ private static bool TryParseAsDateTime( { if (ParseChar(text, start + s_lzyyyy_MM_dd, 'T')) { - if (ParseTimeAndWhitespace(text, start + s_lzyyyy_MM_ddT, out hour, out minute, out second, out fraction)) + if (ParseTimeAndWhitespace(text, start + s_lzyyyy_MM_ddT, out time)) { - parsedValue = new DateAndTimeInfo(date, fraction.Value, hour.Value, XsdDateTimeKind.Unspecified, minute.Value, second.Value, DateTimeTypeCode.XdrDateTime, 0, 0); + parsedValue = new DateAndTimeInfo(date, XsdDateTimeKind.Unspecified, time, DateTimeTypeCode.XdrDateTime, 0, 0); return true; } } else { - parsedValue = new DateAndTimeInfo(date, 0, 0, XsdDateTimeKind.Unspecified, 0, 0, DateTimeTypeCode.XdrDateTime, 0, 0); + time = default; + parsedValue = new DateAndTimeInfo(date, XsdDateTimeKind.Unspecified, time, DateTimeTypeCode.XdrDateTime, 0, 0); return true; } } @@ -203,9 +231,10 @@ private static bool TryParseAsTime( int start, out DateAndTimeInfo parsedValue) { - if (Test(kinds, XsdDateTimeFlags.Time) && TryParseTimeAndZoneAndWhitespace(text, start, out int? hour, out int? minute, out int? second, out int? fraction, out XsdDateTimeKind? kind, out int? zoneHour, out int? zoneMinute)) + if (Test(kinds, XsdDateTimeFlags.Time) + && TryParseTimeAndZoneAndWhitespace(text, start, out XsdDateTimeKind? kind, out TimeInfo time, out int? zoneHour, out int? zoneMinute)) { - parsedValue = new DateAndTimeInfo(DateInfo.DefaultValue, fraction.Value, hour.Value, kind.Value, minute.Value, second.Value, DateTimeTypeCode.Time, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(DateInfo.DefaultValue, kind.Value, time, DateTimeTypeCode.Time, zoneHour.Value, zoneMinute.Value); return true; } @@ -219,9 +248,9 @@ private static bool TryParseAsXdrTimeNoTz( int start, out DateAndTimeInfo parsedValue) { - if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz) && ParseTimeAndWhitespace(text, start, out int? hour, out int? minute, out int? second, out int? fraction)) + if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz) && ParseTimeAndWhitespace(text, start, out TimeInfo time)) { - parsedValue = new DateAndTimeInfo(DateInfo.DefaultValue, fraction.Value, hour.Value, XsdDateTimeKind.Unspecified, minute.Value, second.Value, DateTimeTypeCode.Time, default, default); + parsedValue = new DateAndTimeInfo(DateInfo.DefaultValue, XsdDateTimeKind.Unspecified, time, DateTimeTypeCode.Time, default, default); return true; } @@ -249,19 +278,19 @@ private static bool TryParseAsGYearOrGYearMonth( TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM, out kind, out zoneHour, out zoneMinute)) { date = new DateInfo(DateInfo.FirstDay, month.Value, year.Value); - parsedValue = new DateAndTimeInfo(date, default, default, kind.Value, default, default, DateTimeTypeCode.GYearMonth, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.GYearMonth, zoneHour.Value, zoneMinute.Value); return true; } if (Test(kinds, XsdDateTimeFlags.GYear) && TryParseZoneAndWhitespace(text, start + s_lzyyyy, out kind, out zoneHour, out zoneMinute)) { date = new DateInfo(DateInfo.FirstDay, DateInfo.FirstMonth, year.Value); - parsedValue = new DateAndTimeInfo(date, default, default, kind.Value, default, default, DateTimeTypeCode.GYear, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.GYear, zoneHour.Value, zoneMinute.Value); return true; } } - parsedValue = new DateAndTimeInfo(default, default, default, XsdDateTimeKind.Unspecified, default, default, default, default, default); + parsedValue = new DateAndTimeInfo(default, XsdDateTimeKind.Unspecified, default, default, default, default); return false; } @@ -286,7 +315,7 @@ private static bool TryParseAsGMonthOrGMonthDay( TryParseZoneAndWhitespace(text, start + s_lz__mm_dd, out kind, out zoneHour, out zoneMinute)) { date = new DateInfo(day.Value, month.Value, DateInfo.LeapYear); - parsedValue = new DateAndTimeInfo(date, default, default, kind.Value, default, default, DateTimeTypeCode.GMonthDay, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.GMonthDay, zoneHour.Value, zoneMinute.Value); return true; } @@ -297,7 +326,7 @@ private static bool TryParseAsGMonthOrGMonthDay( TryParseZoneAndWhitespace(text, start + s_lz__mm__, out kind, out zoneHour, out zoneMinute))) { date = new DateInfo(DateInfo.FirstDay, month.Value, DateInfo.LeapYear); - parsedValue = new DateAndTimeInfo(date, default, default, kind.Value, default, default, DateTimeTypeCode.GMonth, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.GMonth, zoneHour.Value, zoneMinute.Value); return true; } } @@ -326,7 +355,7 @@ private static bool TryParseAsGDay( TryParseZoneAndWhitespace(text, start + s_lz___dd, out kind, out zoneHour, out zoneMinute)) { date = new DateInfo(day.Value, DateInfo.FirstMonth, DateInfo.LeapYear); - parsedValue = new DateAndTimeInfo(date, default, default, kind.Value, default, default, DateTimeTypeCode.GDay, zoneHour.Value, zoneMinute.Value); + parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.GDay, zoneHour.Value, zoneMinute.Value); return true; } @@ -422,11 +451,10 @@ private static bool TryParseDate( private static bool TryParseTime( string rawValue, ref int start, - [NotNullWhen(true)] out int? hour, - [NotNullWhen(true)] out int? minute, - [NotNullWhen(true)] out int? second, - [NotNullWhen(true)] out int? fraction) + out TimeInfo time) { + int? fraction, hour, minute, second; + if ( ParseTwoDigits(rawValue, start, out hour) && hour < 24 && ParseChar(rawValue, start + s_lzHH, ':') && @@ -475,6 +503,7 @@ private static bool TryParseTime( { if (fractionDigits == 0) { + time = default; return false; // cannot end with . } fraction *= Power10[MaxFractionDigits - fractionDigits]; @@ -493,25 +522,20 @@ private static bool TryParseTime( fraction = 0; } + time = new TimeInfo(fraction.Value, hour.Value, minute.Value, second.Value); return true; } - hour = default; - minute = default; - second = default; - fraction = default; + time = default; return false; } private static bool ParseTimeAndWhitespace( string rawValue, int start, - [NotNullWhen(true)] out int? hour, - [NotNullWhen(true)] out int? minute, - [NotNullWhen(true)] out int? second, - [NotNullWhen(true)] out int? fraction) + out TimeInfo time) { - if (TryParseTime(rawValue, ref start, out hour, out minute, out second, out fraction)) + if (TryParseTime(rawValue, ref start, out time)) { while (start < rawValue.Length) { @@ -519,21 +543,19 @@ private static bool ParseTimeAndWhitespace( } return start == rawValue.Length; } + return false; } private static bool TryParseTimeAndZoneAndWhitespace( string rawValue, int start, - [NotNullWhen(true)] out int? hour, - [NotNullWhen(true)] out int? minute, - [NotNullWhen(true)] out int? second, - [NotNullWhen(true)] out int? fraction, [NotNullWhen(true)] out XsdDateTimeKind? kind, + out TimeInfo time, [NotNullWhen(true)] out int? zoneHour, [NotNullWhen(true)] out int? zoneMinute) { - if (TryParseTime(rawValue, ref start, out hour, out minute, out second, out fraction)) + if (TryParseTime(rawValue, ref start, out time)) { if (TryParseZoneAndWhitespace(rawValue, start, out kind, out zoneHour, out zoneMinute)) { diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs index a29ad46b78e809..3307a89606e0cd 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs @@ -8,32 +8,23 @@ namespace System.Xml.Schema.DateAndTime.Helpers internal struct DateAndTimeInfo { public DateInfo Date { get; } - public int Fraction { get; } - public int Hour { get; } public XsdDateTimeKind Kind { get; } - public int Minute { get; } - public int Second { get; } + public TimeInfo Time { get; } public DateTimeTypeCode TypeCode { get; } public int ZoneHour { get; } public int ZoneMinute { get; } public DateAndTimeInfo( DateInfo date, - int fraction, - int hour, XsdDateTimeKind kind, - int minute, - int second, + TimeInfo time, DateTimeTypeCode typeCode, int zoneHour, int zoneMinute) { Date = date; - Fraction = fraction; - Hour = hour; Kind = kind; - Minute = minute; - Second = second; + Time = time; TypeCode = typeCode; ZoneHour = zoneHour; ZoneMinute = zoneMinute; @@ -41,13 +32,10 @@ public DateAndTimeInfo( public DateAndTimeInfo() : this( - default, - default, default, XsdDateTimeKind.Unspecified, default, default, - default, 0, 0) { diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/TimeInfo.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/TimeInfo.cs new file mode 100644 index 00000000000000..f3919c9e6a4ab2 --- /dev/null +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/TimeInfo.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Xml.Schema.DateAndTime.Helpers +{ + internal struct TimeInfo + { + public int Fraction { get; } + public int Hour { get; } + public int Microsecond + { + get + { + return Convert.ToInt32((Fraction % TimeSpan.TicksPerMillisecond) / TimeSpan.TicksPerMicrosecond); + } + } + + public int Millisecond + { + get + { + return Convert.ToInt32(Fraction / TimeSpan.TicksPerMillisecond); + } + } + + public int Minute { get; } + public int Second { get; } + + public TimeInfo(int fraction, int hour, int minute, int second) + { + Fraction = fraction; + Hour = hour; + Minute = minute; + Second = second; + } + } +} diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdTime.cs new file mode 100644 index 00000000000000..aaa5d484fe6854 --- /dev/null +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdTime.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Xml.Schema.DateAndTime.Converters; +using System.Xml.Schema.DateAndTime.Helpers; + +namespace System.Xml.Schema.DateAndTime +{ + internal struct XsdTime : IFormattable + { + private TimeOnly Time { get; set; } + + private XsdTime(TimeInfo parsedValue) + : this() + { + Time = new TimeOnly( + parsedValue.Hour, + parsedValue.Minute, + parsedValue.Second, + parsedValue.Millisecond, + parsedValue.Microsecond); + } + + /// + public string ToString(string? format, IFormatProvider? formatProvider) + { + return Time.ToString(format, formatProvider); + } + + internal static bool TryParse(string text, out XsdTime result) + { + if (!DateAndTimeConverter.TryParse(text, out TimeInfo parsedValue)) + { + result = default; + return false; + } + + result = new XsdTime(parsedValue); + return true; + } + } +} diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs index 62bbf4ac542dab..fb1b606cd2c589 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs @@ -94,10 +94,10 @@ private XsdDateTime(DateAndTimeInfo parsedValue) : this() private void InitiateXsdDateTime(DateAndTimeInfo parsedValue) { - _dt = new DateTime(parsedValue.Date.Year, parsedValue.Date.Month, parsedValue.Date.Day, parsedValue.Hour, parsedValue.Minute, parsedValue.Second); - if (parsedValue.Fraction != 0) + _dt = new DateTime(parsedValue.Date.Year, parsedValue.Date.Month, parsedValue.Date.Day, parsedValue.Time.Hour, parsedValue.Time.Minute, parsedValue.Time.Second); + if (parsedValue.Time.Fraction != 0) { - _dt = _dt.AddTicks(parsedValue.Fraction); + _dt = _dt.AddTicks(parsedValue.Time.Fraction); } _extra = (uint)(((int)parsedValue.TypeCode << TypeShift) | ((int)parsedValue.Kind << KindShift) | (parsedValue.ZoneHour << ZoneHourShift) | parsedValue.ZoneMinute); } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs index d4cf9623ce5da3..476b81d5d96393 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs @@ -334,6 +334,30 @@ public static string MSFormatDate(string date, string format, string lang) } } + /// + /// Format xsd:time as a time string for a given language using a given format string. + /// + /// Lexical representation of xsd:time. + /// Format string. + /// Specifies a culture used for formatting. + /// formatted as a time string. + public static string MSFormatTime(string time, string format, string lang) + { + try + { + if (!XsdTime.TryParse(time, out XsdTime xsdTime)) + { + return string.Empty; + } + + return xsdTime.ToString(format.Length != 0 ? format : null, GetCultureInfo(lang)); + } + catch (ArgumentException) + { + return string.Empty; + } + } + public static double MSNumber(IList value) { XsltLibrary.CheckXsltValue(value); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs index 20c91b075c2678..2ab804e892a652 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs @@ -50,6 +50,8 @@ internal static class XsltMethods public static readonly MethodInfo MSFormatDate = typeof(XsltFunctions) .GetMethod(nameof(XsltFunctions.MSFormatDate))!; public static readonly MethodInfo MSFormatDateTime = typeof(XsltFunctions).GetMethod("MSFormatDateTime")!; + public static readonly MethodInfo MSFormatTime = typeof(XsltFunctions) + .GetMethod(nameof(XsltFunctions.MSFormatTime))!; public static readonly MethodInfo MSStringCompare = typeof(XsltFunctions).GetMethod("MSStringCompare")!; public static readonly MethodInfo MSUtc = typeof(XsltFunctions).GetMethod("MSUtc")!; public static readonly MethodInfo MSNumber = typeof(XsltFunctions).GetMethod("MSNumber")!; diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs index 9b1402327407ce..765459cf0acb2f 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs @@ -178,11 +178,10 @@ QilNode IXPathEnvironment.ResolveFunction(string prefix, string name, IList 13:45:30 13:45 - 06/15/2009 13:45:30 + 13:45 01:45:30.876PM 1:45 PM 13:45:30