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..0dd5b4be0cc5e0 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,14 @@ True TextUtf8RawTextWriter.tt + + + + + + + + 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 new file mode 100644 index 00000000000000..102df6e65b1a53 --- /dev/null +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs @@ -0,0 +1,644 @@ +// 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; + + 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 (TryParseAsDateTime(text, kinds, start, out parsedValue)) + { + return true; + } + + 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; + } + + 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; + } + + 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, + int start, + out DateAndTimeInfo parsedValue) + { + 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, 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 kind, out _, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(date, kind.Value, default, 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; + + DateInfo date; + 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 + 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 kind, out time, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(date, kind.Value, time, DateTimeTypeCode.DateTime, 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)) + { + 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 kind, out time, out zoneHour, out zoneMinute)) + { + parsedValue = new DateAndTimeInfo(date, kind.Value, time, DateTimeTypeCode.XdrDateTime, 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 time)) + { + parsedValue = new DateAndTimeInfo(date, XsdDateTimeKind.Unspecified, time, DateTimeTypeCode.XdrDateTime, 0, 0); + return true; + } + } + else + { + time = default; + parsedValue = new DateAndTimeInfo(date, XsdDateTimeKind.Unspecified, time, DateTimeTypeCode.XdrDateTime, 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 XsdDateTimeKind? kind, out TimeInfo time, out int? zoneHour, out int? zoneMinute)) + { + parsedValue = new DateAndTimeInfo(DateInfo.DefaultValue, kind.Value, time, DateTimeTypeCode.Time, 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 TimeInfo time)) + { + parsedValue = new DateAndTimeInfo(DateInfo.DefaultValue, XsdDateTimeKind.Unspecified, time, DateTimeTypeCode.Time, default, default); + return true; + } + + parsedValue = default; + return false; + } + + private static bool TryParseAsGYearOrGYearMonth( + string text, + XsdDateTimeFlags kinds, + int start, + out DateAndTimeInfo parsedValue) + { + int? month, year, zoneHour, zoneMinute; + DateInfo date; + 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)) + { + date = new DateInfo(DateInfo.FirstDay, month.Value, year.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, kind.Value, default, DateTimeTypeCode.GYear, zoneHour.Value, zoneMinute.Value); + return true; + } + } + + parsedValue = new DateAndTimeInfo(default, XsdDateTimeKind.Unspecified, default, default, default, default); + return false; + } + + private static bool TryParseAsGMonthOrGMonthDay( + string text, + XsdDateTimeFlags kinds, + int start, + out DateAndTimeInfo parsedValue) + { + int? day, month, zoneHour, zoneMinute; + DateInfo date; + 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(DateInfo.LeapYear, month.Value) && + 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, kind.Value, default, DateTimeTypeCode.GMonthDay, 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))) + { + date = new DateInfo(DateInfo.FirstDay, month.Value, DateInfo.LeapYear); + parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.GMonth, 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; + DateInfo date; + 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(DateInfo.LeapYear, DateInfo.FirstMonth) && + 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, kind.Value, default, DateTimeTypeCode.GDay, 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, + out DateInfo parsedDate) + { + int? day, month, year; + + 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) + ) + { + parsedDate = new DateInfo(day.Value, month.Value, year.Value); + return true; + } + + parsedDate = default; + return false; + } + + private static bool TryParseTime( + string rawValue, + ref int start, + out TimeInfo time) + { + int? fraction, hour, minute, second; + + 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) + { + time = default; + return false; // cannot end with . + } + fraction *= Power10[MaxFractionDigits - fractionDigits]; + } + else + { + if (round < 0) + { + round = fraction.Value & 1; + } + fraction += round; + } + } + else + { + fraction = 0; + } + + time = new TimeInfo(fraction.Value, hour.Value, minute.Value, second.Value); + return true; + } + + time = default; + return false; + } + + private static bool ParseTimeAndWhitespace( + string rawValue, + int start, + out TimeInfo time) + { + if (TryParseTime(rawValue, ref start, out time)) + { + while (start < rawValue.Length) + { + start++; + } + return start == rawValue.Length; + } + + return false; + } + + private static bool TryParseTimeAndZoneAndWhitespace( + string rawValue, + int start, + [NotNullWhen(true)] out XsdDateTimeKind? kind, + out TimeInfo time, + [NotNullWhen(true)] out int? zoneHour, + [NotNullWhen(true)] out int? zoneMinute) + { + if (TryParseTime(rawValue, ref start, out time)) + { + 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..3307a89606e0cd --- /dev/null +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs @@ -0,0 +1,44 @@ +// 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 DateInfo Date { get; } + public XsdDateTimeKind Kind { get; } + public TimeInfo Time { get; } + public DateTimeTypeCode TypeCode { get; } + public int ZoneHour { get; } + public int ZoneMinute { get; } + + public DateAndTimeInfo( + DateInfo date, + XsdDateTimeKind kind, + TimeInfo time, + DateTimeTypeCode typeCode, + int zoneHour, + int zoneMinute) + { + Date = date; + Kind = kind; + Time = time; + TypeCode = typeCode; + ZoneHour = zoneHour; + ZoneMinute = zoneMinute; + } + + public DateAndTimeInfo() + : this( + default, + XsdDateTimeKind.Unspecified, + 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/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/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/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/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 c8c301cf323830..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 @@ -1,11 +1,12 @@ // 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.Converters; +using System.Xml.Schema.DateAndTime.Helpers; +using System.Xml.Schema.DateAndTime.Specifications; namespace System.Xml.Schema { @@ -48,30 +49,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 +58,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,38 +79,38 @@ private enum XsdDateTimeKind /// public XsdDateTime(string text, XsdDateTimeFlags kinds) : this() { - Parser 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(Parser parser) : this() + private XsdDateTime(DateAndTimeInfo parsedValue) : this() { - InitiateXsdDateTime(parser); + InitiateXsdDateTime(parsedValue); } - private void InitiateXsdDateTime(Parser 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.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(parser.fraction); + _dt = _dt.AddTicks(parsedValue.Time.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) { - Parser 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; } @@ -543,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(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[s_lzyyyy] = '-'; - Write2Digits(text, s_lzyyyy_, month); - text[s_lzyyyy_MM] = '-'; - Write2Digits(text, 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 @@ -609,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(s_lzHH_mm_ss); + Span text = vsb.AppendSpan(DateAndTimeConverter.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[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 = MaxFractionDigits; + int fractionDigits = DateAndTimeConverter.MaxFractionDigits; while (fraction % 10 == 0) { fractionDigits--; @@ -641,18 +593,18 @@ private void PrintZone(ref ValueStringBuilder vsb) vsb.Append('Z'); break; case XsdDateTimeKind.LocalWestOfZulu: - text = vsb.AppendSpan(s_lz_zz_zz); + text = vsb.AppendSpan(DateAndTimeConverter.s_lz_zz_zz); text[0] = '-'; - Write2Digits(text, s_Lz_, ZoneHour); - text[s_lz_zz] = ':'; - Write2Digits(text, 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(s_lz_zz_zz); + text = vsb.AppendSpan(DateAndTimeConverter.s_lz_zz_zz); text[0] = '+'; - Write2Digits(text, s_Lz_, ZoneHour); - text[s_lz_zz] = ':'; - Write2Digits(text, 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 @@ -688,388 +640,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; - } - } } } 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..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 @@ -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,54 @@ 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; + } + } + + /// + /// 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); @@ -351,7 +400,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 +427,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..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 @@ -47,7 +47,11 @@ 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 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 f0410142b8ca5a..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 @@ -166,14 +166,22 @@ QilNode IXPathEnvironment.ResolveFunction(string prefix, string name, IList - - 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 + 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 + 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 19f20d615e9684..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 88932a64469ab6..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 @@ -1,48 +1,51 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 00511a00ec6f19..d586c2dab8c5b6 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; @@ -3106,6 +3108,252 @@ public void TransformStrStrResolver3(object param, XslInputType xslInputType, Re [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] public class CTransformConstructorWithFourParametersTest : XsltApiTestCaseBase2 { + public static IEnumerable LoadXmlFileExternalUriShouldThrowXsltExceptionTestData + { + get + { + yield return new object[] { + 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[] { + 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[] { + 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[] { + 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[] { + 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[] { + 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[] { + 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[] { + 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[] { + 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[] { + 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[] { + FullFilePath("Bug382198.xsl"), + navigator5, + Path.Combine("baseline", "bug382198.txt"), + new XmlUrlResolver() + }; + + XmlDocument xmlDoc6 = new XmlDocument(); + xmlDoc6.Load(FullFilePath("fruits.xml")); + yield return new object[] { + 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[] { + FullFilePath("Bug382198.xsl"), + navigator6, + Path.Combine("baseline", "bug382198.txt"), + null + }; + } + } + + public static IEnumerable TransformWithIXPathNavigableXmlFileExternalUriReturnsExpectedBaseLineFileTestData + { + get + { + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.Load(FullFilePath("fruits.xml")); + yield return new object[] { + 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[] { + 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[] { + FullFilePath("XmlResolver_Main.xsl"), + FullFilePath("fruits.xml"), + Path.Combine("baseline", "xmlResolver_main.txt"), + new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), "XsltApiV2"))) + }; + } + } + private ITestOutputHelper _output; public CTransformConstructorWithFourParametersTest(ITestOutputHelper output) : base(output) { @@ -3129,125 +3377,80 @@ 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(TransformWithIXPathNavigableXmlFileExternalUriReturnsExpectedBaseLineFileTestData))] + public void TransformWithIXPathNavigable_XmlFileExternalUri_ReturnsExpectedBaseLineFile(string xslFile, IXPathNavigable navigator, string baseLineFile, XmlResolver resolver) { using (new AllowDefaultResolverContext()) { - ValidCases(param0, param1, param2, param3, param4, param5); - } - } - - //[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) - { - 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; - - default: - break; + TransformWithIXPathNavigable_XmlFile_ReturnsExpectedBaseLineFile(xslFile, navigator, baseLineFile, resolver); } + } - XmlResolver resolver = null; - switch (param3 as string) + [Theory] + [MemberData(nameof(TransformWithXmlReaderXmlFileExternalUriReturnsExpectedBaseLineFileTestData))] + public void TransformWithXmlReader_XmlFileExternalUri_ReturnsExpectedBaseLineFile(string xslFile, string xmlFile, string baseLineFile, XmlResolver resolver) + { + using (new AllowDefaultResolverContext()) { - case "NullResolver": - break; - - case "XmlUrlResolver": - resolver = new XmlUrlResolver(); - break; - - case "CustomXmlResolver": - resolver = new CustomXmlResolver(Path.GetFullPath(Path.Combine(FilePathUtil.GetTestDataPath(), @"XsltApiV2"))); - break; + TransformWithXmlReader_XmlFile_ReturnsExpectedBaseLineFile(xslFile, xmlFile, baseLineFile, resolver); + } + } - default: - 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)); } + } - try + [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); + } 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); + localXslt.Transform(navigator, null, writer, resolver); } VerifyResult(baseLineFile, "outputFile.txt"); - actualResult = true; } - catch (Exception ex) + } + + [Theory] + [MemberData(nameof(TransformWithXmlReaderXmlFileReturnsExpectedBaseLineFileCasesTestData))] + public void TransformWithXmlReader_XmlFile_ReturnsExpectedBaseLineFile(string xslFile, string xmlFile, string baseLineFile, XmlResolver resolver) + { + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) { - _output.WriteLine(ex.Message); - actualResult = false; - } + XmlReader xmlReader = XmlReader.Create(xmlFile); - if (actualResult != expectedResult) - Assert.Fail(); + XslCompiledTransform localXslt = new XslCompiledTransform(); + XsltSettings settings = new XsltSettings(true, true); + using (XmlReader xslReader = XmlReader.Create(xslFile)) + { + localXslt.Load(xslReader, settings, resolver); + } - return; + using (XmlWriter writer = XmlWriter.Create("outputFile.txt")) + { + localXslt.Transform(xmlReader, null, writer, resolver); + } + VerifyResult(baseLineFile, "outputFile.txt"); + } } //[Variation("Invalid Arguments: null, valid, valid, valid", Pri = 0, Params = new object[] { 1, false })] 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; } }