From b345e2dbd6936eb281fadb7d70473358be578d24 Mon Sep 17 00:00:00 2001 From: Meri Khamoyan <96171496+mkhamoyan@users.noreply.github.com> Date: Wed, 6 Dec 2023 11:50:53 +0100 Subject: [PATCH] [iOS][non-icu] HybridGlobalization implement GetSortKey on hybrid mode (#95260) Implement GetSortKey on hybrid mode --- .../features/globalization-hybrid-mode.md | 4 +- .../src/Interop/Interop.Collation.iOS.cs | 3 + .../Common/tests/Tests/System/StringTests.cs | 26 +- .../System/Globalization/CompareInfo.Icu.cs | 72 +++++- .../src/System/Globalization/CompareInfo.cs | 8 +- .../CompareInfo/CompareInfoTests.cs | 233 +++++++++--------- .../Hybrid/System.Runtime.IOS.Tests.csproj | 4 + .../System/StringGetHashCodeTests.cs | 3 +- .../System/StringTests.cs | 22 +- .../System.Globalization.Native/entrypoints.c | 1 + .../pal_collation.h | 10 +- .../pal_collation.m | 39 +++ 12 files changed, 277 insertions(+), 148 deletions(-) diff --git a/docs/design/features/globalization-hybrid-mode.md b/docs/design/features/globalization-hybrid-mode.md index 7966be7224710..2be75ac3150e6 100644 --- a/docs/design/features/globalization-hybrid-mode.md +++ b/docs/design/features/globalization-hybrid-mode.md @@ -463,7 +463,9 @@ Affected public APIs: - CompareInfo.GetSortKeyLength - CompareInfo.GetHashCode -Apple Native API does not have an equivalent, so they throw `PlatformNotSupportedException`. +Implemeneted using [stringByFoldingWithOptions:locale:](https://developer.apple.com/documentation/foundation/nsstring/1413779-stringbyfoldingwithoptions) + +Note: This implementation does not construct SortKeys like ICU ucol_getSortKey does, and might not adhere to the specifications specifications of SortKey such as SortKeys from different collators not being comparable and merging sortkeys. ## Case change diff --git a/src/libraries/Common/src/Interop/Interop.Collation.iOS.cs b/src/libraries/Common/src/Interop/Interop.Collation.iOS.cs index 70e907efa68a1..2dba31ddcd70e 100644 --- a/src/libraries/Common/src/Interop/Interop.Collation.iOS.cs +++ b/src/libraries/Common/src/Interop/Interop.Collation.iOS.cs @@ -29,5 +29,8 @@ internal static partial class Globalization [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_StartsWithNative", StringMarshalling = StringMarshalling.Utf16)] [MethodImpl(MethodImplOptions.NoInlining)] internal static unsafe partial int StartsWithNative(string localeName, int lNameLen, char* target, int cwTargetLength, char* source, int cwSourceLength, CompareOptions options); + + [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetSortKeyNative", StringMarshalling = StringMarshalling.Utf16)] + internal static unsafe partial int GetSortKeyNative(string localeName, int lNameLen, char* str, int strLength, byte* sortKey, int sortKeyLength, CompareOptions options); } } diff --git a/src/libraries/Common/tests/Tests/System/StringTests.cs b/src/libraries/Common/tests/Tests/System/StringTests.cs index 119e4b7d1af44..c0b31d5d352c3 100644 --- a/src/libraries/Common/tests/Tests/System/StringTests.cs +++ b/src/libraries/Common/tests/Tests/System/StringTests.cs @@ -1009,6 +1009,7 @@ public static void MakeSureNoCompareToChecksGoOutOfRange_StringComparison() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnOSX))] public static void CompareToNoMatch_StringComparison() { for (int length = 1; length < 150; length++) @@ -1283,6 +1284,7 @@ public static void ContainsMatchDifferentSpans_StringComparison() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnOSX))] public static void ContainsNoMatch_StringComparison() { for (int length = 1; length < 150; length++) @@ -1660,7 +1662,7 @@ public static IEnumerable EndsWith_StringComparison_TestData() yield return new object[] { "", "", StringComparison.CurrentCulture, true }; yield return new object[] { "", "a", StringComparison.CurrentCulture, false }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "Hello", "llo" + SoftHyphen, StringComparison.CurrentCulture, true }; // CurrentCultureIgnoreCase @@ -1672,7 +1674,7 @@ public static IEnumerable EndsWith_StringComparison_TestData() yield return new object[] { "", "", StringComparison.CurrentCultureIgnoreCase, true }; yield return new object[] { "", "a", StringComparison.CurrentCultureIgnoreCase, false }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "Hello", "llo" + SoftHyphen, StringComparison.CurrentCultureIgnoreCase, true }; // InvariantCulture @@ -1685,7 +1687,7 @@ public static IEnumerable EndsWith_StringComparison_TestData() yield return new object[] { "", "", StringComparison.InvariantCulture, true }; yield return new object[] { "", "a", StringComparison.InvariantCulture, false }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "Hello", "llo" + SoftHyphen, StringComparison.InvariantCulture, true }; // InvariantCultureIgnoreCase @@ -1697,7 +1699,7 @@ public static IEnumerable EndsWith_StringComparison_TestData() yield return new object[] { "", "", StringComparison.InvariantCultureIgnoreCase, true }; yield return new object[] { "", "a", StringComparison.InvariantCultureIgnoreCase, false }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "Hello", "llo" + SoftHyphen, StringComparison.InvariantCultureIgnoreCase, true }; // Ordinal @@ -2109,6 +2111,7 @@ public static void EndsWithMatchDifferentSpans_StringComparison() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnOSX))] public static void EndsWithNoMatch_StringComparison() { for (int length = 1; length < 150; length++) @@ -3194,7 +3197,7 @@ public static void IndexOf_TurkishI_EnglishUSCulture() } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnOSX))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60568", TestPlatforms.Android | TestPlatforms.LinuxBionic)] public static void IndexOf_HungarianDoubleCompression_HungarianCulture() { @@ -4849,7 +4852,7 @@ public static IEnumerable StartsWith_StringComparison_TestData() yield return new object[] { "", "", StringComparison.CurrentCulture, true }; yield return new object[] { "", "hello", StringComparison.CurrentCulture, false }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "Hello", SoftHyphen + "Hel", StringComparison.CurrentCulture, true }; // CurrentCultureIgnoreCase @@ -4861,7 +4864,7 @@ public static IEnumerable StartsWith_StringComparison_TestData() yield return new object[] { "", "", StringComparison.CurrentCultureIgnoreCase, true }; yield return new object[] { "", "hello", StringComparison.CurrentCultureIgnoreCase, false }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "Hello", SoftHyphen + "Hel", StringComparison.CurrentCultureIgnoreCase, true }; // InvariantCulture @@ -4873,7 +4876,7 @@ public static IEnumerable StartsWith_StringComparison_TestData() yield return new object[] { "", "", StringComparison.InvariantCulture, true }; yield return new object[] { "", "hello", StringComparison.InvariantCulture, false }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "Hello", SoftHyphen + "Hel", StringComparison.InvariantCulture, true }; // InvariantCultureIgnoreCase @@ -4885,7 +4888,7 @@ public static IEnumerable StartsWith_StringComparison_TestData() yield return new object[] { "", "", StringComparison.InvariantCultureIgnoreCase, true }; yield return new object[] { "", "hello", StringComparison.InvariantCultureIgnoreCase, false }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "Hello", SoftHyphen + "Hel", StringComparison.InvariantCultureIgnoreCase, true }; // Ordinal @@ -5342,6 +5345,7 @@ private static IEnumerable ToLower_Culture_TestData() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnOSX))] public static void Test_ToLower_Culture() { foreach (object[] testdata in ToLower_Culture_TestData()) @@ -5857,6 +5861,7 @@ public static IEnumerable ToUpper_Culture_TestData() } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnOSX))] [MemberData(nameof(ToUpper_Culture_TestData))] public static void Test_ToUpper_Culture(string actual, string expected, CultureInfo culture) { @@ -5955,7 +5960,7 @@ public static IEnumerable ToUpper_TurkishI_InvariantCulture_MemberData new KeyValuePair('\u0130', '\u0130'), new KeyValuePair('\u0131', '\u0131')); - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnOSX))] [MemberData(nameof(ToUpper_TurkishI_InvariantCulture_MemberData))] public static void ToUpper_TurkishI_InvariantCulture(string s, string expected) { @@ -7225,6 +7230,7 @@ public static void StartsWithMatchDifferentSpans_StringComparison() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnOSX))] public static void StartsWithNoMatch_StringComparison() { for (int length = 1; length < 150; length++) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs index 7a4fb0289c464..f2c7dd254ae2f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs @@ -700,14 +700,36 @@ private unsafe SortKey IcuCreateSortKey(string source, CompareOptions options) byte[] keyData; fixed (char* pSource = source) { - int sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, null, 0, options); + int sortKeyLength; +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + { + sortKeyLength = Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, source.Length, null, 0, options); + } + else +#endif + { + sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, null, 0, options); + } keyData = new byte[sortKeyLength]; fixed (byte* pSortKey = keyData) { - if (Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength) +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) { - throw new ArgumentException(SR.Arg_ExternalException); + if (Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength) + { + throw new ArgumentException(SR.Arg_ExternalException); + } + } + else +#endif + { + if (Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength) + { + throw new ArgumentException(SR.Arg_ExternalException); + } } } } @@ -728,7 +750,16 @@ private unsafe int IcuGetSortKey(ReadOnlySpan source, Span destinati fixed (char* pSource = &MemoryMarshal.GetReference(source)) fixed (byte* pDest = &MemoryMarshal.GetReference(destination)) { - actualSortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pDest, destination.Length, options); +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + { + actualSortKeyLength = Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, source.Length, pDest, destination.Length, options); + } + else +#endif + { + actualSortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pDest, destination.Length, options); + } } // The check below also handles errors due to negative values / overflow being returned. @@ -758,7 +789,16 @@ private unsafe int IcuGetSortKeyLength(ReadOnlySpan source, CompareOptions fixed (char* pSource = &MemoryMarshal.GetReference(source)) { - return Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, null, 0, options); +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + { + return Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, source.Length, null, 0, options); + } + else +#endif + { + return Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, null, 0, options); + } } } @@ -809,7 +849,16 @@ private unsafe int IcuGetHashCodeOfString(ReadOnlySpan source, CompareOpti { fixed (byte* pSortKey = &MemoryMarshal.GetReference(sortKey)) { - sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKey.Length, options); +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + { + sortKeyLength = Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, source.Length, pSortKey, sortKey.Length, options); + } + else +#endif + { + sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKey.Length, options); + } } if (sortKeyLength > sortKey.Length) // slow path for big strings @@ -823,7 +872,16 @@ private unsafe int IcuGetHashCodeOfString(ReadOnlySpan source, CompareOpti fixed (byte* pSortKey = &MemoryMarshal.GetReference(sortKey)) { - sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKey.Length, options); +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + { + sortKeyLength = Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, source.Length, pSortKey, sortKey.Length, options); + } + else +#endif + { + sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKey.Length, options); + } } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs index 1d7a313d14add..538e4b3551ca1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs @@ -1450,7 +1450,7 @@ public SortKey GetSortKey(string source) private SortKey CreateSortKeyCore(string source, CompareOptions options) => GlobalizationMode.UseNls ? NlsCreateSortKey(source, options) : -#if TARGET_BROWSER || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS +#if TARGET_BROWSER GlobalizationMode.Hybrid ? throw new PlatformNotSupportedException(GetPNSEText("SortKey")) : #endif @@ -1493,7 +1493,7 @@ public int GetSortKey(ReadOnlySpan source, Span destination, Compare private int GetSortKeyCore(ReadOnlySpan source, Span destination, CompareOptions options) => GlobalizationMode.UseNls ? NlsGetSortKey(source, destination, options) : -#if TARGET_BROWSER || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS +#if TARGET_BROWSER GlobalizationMode.Hybrid ? throw new PlatformNotSupportedException(GetPNSEText("SortKey")) : #endif @@ -1530,7 +1530,7 @@ public int GetSortKeyLength(ReadOnlySpan source, CompareOptions options = private int GetSortKeyLengthCore(ReadOnlySpan source, CompareOptions options) => GlobalizationMode.UseNls ? NlsGetSortKeyLength(source, options) : -#if TARGET_BROWSER || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS +#if TARGET_BROWSER GlobalizationMode.Hybrid ? throw new PlatformNotSupportedException(GetPNSEText("SortKey")) : #endif @@ -1607,7 +1607,7 @@ public int GetHashCode(ReadOnlySpan source, CompareOptions options) private unsafe int GetHashCodeOfStringCore(ReadOnlySpan source, CompareOptions options) => GlobalizationMode.UseNls ? NlsGetHashCodeOfString(source, options) : -#if TARGET_BROWSER || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS +#if TARGET_BROWSER GlobalizationMode.Hybrid ? throw new PlatformNotSupportedException(GetPNSEText("HashCode")) : #endif diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.cs index cbd011b96fa0c..8ca1892cde0c8 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.cs @@ -60,7 +60,7 @@ public void EqualsTest(CompareInfo compare1, object value, bool expected) new object[] { "", CompareOptions.None, "\u200c", CompareOptions.None, true }, // see comment at bottom of SortKey_TestData }; - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalization))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] [MemberData(nameof(GetHashCodeTestData))] public void GetHashCodeTest(string source1, CompareOptions options1, string source2, CompareOptions options2, bool expected) { @@ -114,72 +114,75 @@ public static IEnumerable SortKey_Kana_TestData() public static IEnumerable SortKey_TestData() { - CompareOptions ignoreKanaIgnoreWidthIgnoreCase = CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; - yield return new object[] { s_invariantCompare, "\u3042", "\u30A2", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u3042", "\uFF71", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u30E3", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u3083", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u304D \u3083", "\u30AD\u3083", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3044", "I", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - - yield return new object[] { s_invariantCompare, "a", "A", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "a", "\uFF41", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23\uFF24\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23D\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "a\uFF22\uFF23D\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF41\uFF42\uFF23D\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - - yield return new object[] { s_invariantCompare, "\u6FA4", "\u6CA2", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u30D6\u30D9\u30DC", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\u30DC", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u3079\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D6", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3071\u3074\u30D7\u307A", "\uFF8B\uFF9F\uFF8C\uFF9F", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u3070\uFF8E\uFF9E\u30D6", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C\u3079\u307C", "\u3079\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D6", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - - yield return new object[] { s_invariantCompare, "ABDDE", "D", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF43D", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "ABCDE", "c", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3060", "\u305F", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30BF", "\uFF80", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - - yield return new object[] { s_invariantCompare, "\u68EE\u9D0E\u5916", "\u68EE\u9DD7\u5916", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u68EE\u9DD7\u5916", "\u68EE\u9DD7\u5916", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u2019\u2019\u2019\u2019", "''''", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u2019", "'", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "", "'", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u4E00", "\uFF11", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u2160", "\uFF11", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - - yield return new object[] { s_invariantCompare, "0", "\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "10", "1\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "9999\uFF1910", "1\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "9999\uFF191010", "1\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - - yield return new object[] { s_invariantCompare, "'\u3000'", "' '", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\uFF1B", ";", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\uFF08", "(", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\uFF70", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\uFF0D", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\u30FC", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\u2015", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\u2010", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - - yield return new object[] { s_invariantCompare, "/", "\uFF0F", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\"", "\uFF02", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - - if (!PlatformDetection.IsWindows7) + if (PlatformDetection.IsNotHybridGlobalizationOnOSX) { - // For the below string, LCMapStringEx and CompareStringEx on Windows 7 return inconsistent results. - // We'll only run this test case on Win8+ or on non-Windows machines. - yield return new object[] { s_invariantCompare, "'", "\uFF07", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + CompareOptions ignoreKanaIgnoreWidthIgnoreCase = CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; + yield return new object[] { s_invariantCompare, "\u3042", "\u30A2", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u3042", "\uFF71", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + + yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u30E3", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u3083", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u304D \u3083", "\u30AD\u3083", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "\u3044", "I", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + + yield return new object[] { s_invariantCompare, "a", "A", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "a", "\uFF41", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23\uFF24\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23D\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "a\uFF22\uFF23D\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF41\uFF42\uFF23D\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + + yield return new object[] { s_invariantCompare, "\u6FA4", "\u6CA2", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + + yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u30D6\u30D9\u30DC", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\u30DC", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u3079\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D6", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "\u3071\u3074\u30D7\u307A", "\uFF8B\uFF9F\uFF8C\uFF9F", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u3070\uFF8E\uFF9E\u30D6", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C\u3079\u307C", "\u3079\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "\u3070\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D6", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + + yield return new object[] { s_invariantCompare, "ABDDE", "D", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF43D", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "ABCDE", "c", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "\u3060", "\u305F", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30BF", "\uFF80", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + + yield return new object[] { s_invariantCompare, "\u68EE\u9D0E\u5916", "\u68EE\u9DD7\u5916", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "\u68EE\u9DD7\u5916", "\u68EE\u9DD7\u5916", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u2019\u2019\u2019\u2019", "''''", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + yield return new object[] { s_invariantCompare, "\u2019", "'", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + yield return new object[] { s_invariantCompare, "", "'", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "\u4E00", "\uFF11", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + yield return new object[] { s_invariantCompare, "\u2160", "\uFF11", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + + yield return new object[] { s_invariantCompare, "0", "\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "10", "1\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "9999\uFF1910", "1\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + yield return new object[] { s_invariantCompare, "9999\uFF191010", "1\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + + yield return new object[] { s_invariantCompare, "'\u3000'", "' '", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\uFF1B", ";", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\uFF08", "(", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30FC", "\uFF70", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30FC", "\uFF0D", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + yield return new object[] { s_invariantCompare, "\u30FC", "\u30FC", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30FC", "\u2015", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + yield return new object[] { s_invariantCompare, "\u30FC", "\u2010", ignoreKanaIgnoreWidthIgnoreCase, 1 }; + + yield return new object[] { s_invariantCompare, "/", "\uFF0F", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\"", "\uFF02", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + + if (!PlatformDetection.IsWindows7) + { + // For the below string, LCMapStringEx and CompareStringEx on Windows 7 return inconsistent results. + // We'll only run this test case on Win8+ or on non-Windows machines. + yield return new object[] { s_invariantCompare, "'", "\uFF07", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + } } yield return new object[] { s_invariantCompare, "\u3042", "\u30A1", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; @@ -189,13 +192,13 @@ public static IEnumerable SortKey_TestData() yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u3083", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; yield return new object[] { s_invariantCompare, "\u304D \u3083", "\u30AD\u3083", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "\u3044", "I", CompareOptions.None, 1 }; - yield return new object[] { s_invariantCompare, "a", "A", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "a", "\uFF41", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23\uFF24\uFF25", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23D\uFF25", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "\u3044", "I", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; + yield return new object[] { s_invariantCompare, "a", "A", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; + yield return new object[] { s_invariantCompare, "a", "\uFF41", CompareOptions.None,PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23\uFF24\uFF25", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23D\uFF25", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; yield return new object[] { s_invariantCompare, new string('a', 5555), new string('a', 5554) + "b", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF41\uFF42\uFF23D\uFF25", CompareOptions.None, 1 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF41\uFF42\uFF23D\uFF25", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; yield return new object[] { s_invariantCompare, "\u6FA4", "\u6CA2", CompareOptions.None, 1 }; yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u30D6\u30D9\u30DC", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; @@ -219,83 +222,87 @@ public static IEnumerable SortKey_TestData() yield return new object[] { s_invariantCompare, "\u68EE\u9D0E\u5916", "\u68EE\u9DD7\u5916", CompareOptions.None, -1 }; yield return new object[] { s_invariantCompare, "\u68EE\u9DD7\u5916", "\u68EE\u9DD7\u5916", CompareOptions.None, 0 }; - yield return new object[] { s_invariantCompare, "\u2019\u2019\u2019\u2019", "''''", CompareOptions.None, 1 }; - yield return new object[] { s_invariantCompare, "\u2019\u2019\u2019\u2019", "''''", CompareOptions.None, 1 }; - yield return new object[] { s_invariantCompare, "\u2019\u2019\u2019\u2019", "''''", CompareOptions.None, 1 }; - yield return new object[] { s_invariantCompare, "\u2019", "'", CompareOptions.None, 1 }; + yield return new object[] { s_invariantCompare, "\u2019\u2019\u2019\u2019", "''''", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; + yield return new object[] { s_invariantCompare, "\u2019\u2019\u2019\u2019", "''''", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; + yield return new object[] { s_invariantCompare, "\u2019\u2019\u2019\u2019", "''''", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; + yield return new object[] { s_invariantCompare, "\u2019", "'", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; yield return new object[] { s_invariantCompare, "", "'", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "\u4E00", "\uFF11", CompareOptions.None, 1 }; + yield return new object[] { s_invariantCompare, "\u4E00", "\uFF11", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; yield return new object[] { s_invariantCompare, "\u2160", "\uFF11", CompareOptions.None, 1 }; - yield return new object[] { s_invariantCompare, "0", "\uFF10", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "10", "1\uFF10", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "0", "\uFF10", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; + yield return new object[] { s_invariantCompare, "10", "1\uFF10", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; yield return new object[] { s_invariantCompare, "1\uFF10", "1\uFF10", CompareOptions.None, 0 }; yield return new object[] { s_invariantCompare, "9999\uFF1910", "1\uFF10", CompareOptions.None, 1 }; yield return new object[] { s_invariantCompare, "9999\uFF191010", "1\uFF10", CompareOptions.None, 1 }; - yield return new object[] { s_invariantCompare, "'\u3000'", "' '", CompareOptions.None, 1 }; - yield return new object[] { s_invariantCompare, "\uFF1B", ";", CompareOptions.None, 1 }; - yield return new object[] { s_invariantCompare, "\uFF08", "(", CompareOptions.None, 1 }; + yield return new object[] { s_invariantCompare, "'\u3000'", "' '", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; + yield return new object[] { s_invariantCompare, "\uFF1B", ";", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; + yield return new object[] { s_invariantCompare, "\uFF08", "(", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; yield return new object[] { s_invariantCompare, "\u30FC", "\uFF0D", CompareOptions.None, 1 }; yield return new object[] { s_invariantCompare, "\u30FC", "\u30FC", CompareOptions.None, 0 }; yield return new object[] { s_invariantCompare, "\u30FC", "\u2015", CompareOptions.None, 1 }; yield return new object[] { s_invariantCompare, "\u30FC", "\u2010", CompareOptions.None, 1 }; - yield return new object[] { s_invariantCompare, "/", "\uFF0F", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "'", "\uFF07", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "\"", "\uFF02", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "/", "\uFF0F", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; + yield return new object[] { s_invariantCompare, "'", "\uFF07", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; + yield return new object[] { s_invariantCompare, "\"", "\uFF02", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; // Turkish yield return new object[] { s_turkishCompare, "i", "I", CompareOptions.None, 1 }; // Android has its own ICU, which doesn't work well with tr - if (!PlatformDetection.IsAndroid && !PlatformDetection.IsLinuxBionic) + if (!PlatformDetection.IsAndroid && !PlatformDetection.IsLinuxBionic && PlatformDetection.IsNotHybridGlobalizationOnOSX) { yield return new object[] { s_turkishCompare, "i", "I", CompareOptions.IgnoreCase, 1 }; yield return new object[] { s_turkishCompare, "i", "\u0130", CompareOptions.IgnoreCase, 0 }; } - yield return new object[] { s_invariantCompare, "i", "\u0130", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "i", "I", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "i", "\u0130", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; + yield return new object[] { s_invariantCompare, "i", "I", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; yield return new object[] { s_invariantCompare, "i", "I", CompareOptions.IgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "i", "\u0130", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "i", "\u0130", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; yield return new object[] { s_invariantCompare, "i", "\u0130", CompareOptions.IgnoreCase, -1 }; yield return new object[] { s_invariantCompare, "\u00C0", "A\u0300", CompareOptions.None, 0 }; - yield return new object[] { s_invariantCompare, "\u00C0", "a\u0300", CompareOptions.None, 1 }; + yield return new object[] { s_invariantCompare, "\u00C0", "a\u0300", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? - 1 : 1 }; yield return new object[] { s_invariantCompare, "\u00C0", "a\u0300", CompareOptions.IgnoreCase, 0 }; yield return new object[] { s_invariantCompare, "FooBA\u0300R", "FooB\u00C0R", CompareOptions.IgnoreNonSpace, 0 }; - yield return new object[] { s_invariantCompare, "Test's", "Tests", CompareOptions.IgnoreSymbols, 0 }; - yield return new object[] { s_invariantCompare, "Test's", "Tests", CompareOptions.StringSort, -1 }; - yield return new object[] { s_invariantCompare, new string('a', 5555), new string('a', 5555), CompareOptions.None, 0 }; yield return new object[] { s_invariantCompare, "foobar", "FooB\u00C0R", CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "foobar", "FooB\u00C0R", CompareOptions.IgnoreNonSpace, -1 }; + yield return new object[] { s_invariantCompare, "foobar", "FooB\u00C0R", CompareOptions.IgnoreNonSpace, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; - yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", CompareOptions.IgnoreNonSpace, 0 }; - yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", CompareOptions.IgnoreCase, 0 }; yield return new object[] { s_invariantCompare, "\u20A9", "\uFFE6", CompareOptions.IgnoreWidth, 0 }; yield return new object[] { s_invariantCompare, "\u20A9", "\uFFE6", CompareOptions.IgnoreCase, -1 }; yield return new object[] { s_invariantCompare, "\u20A9", "\uFFE6", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.IgnoreSymbols, 0 }; - yield return new object[] { s_invariantCompare, "\u00A2", "\uFFE0", CompareOptions.IgnoreSymbols, 0 }; - yield return new object[] { s_invariantCompare, "$", "&", CompareOptions.IgnoreSymbols, 0 }; - yield return new object[] { s_invariantCompare, "\uFF65", "\u30FB", CompareOptions.IgnoreSymbols, 0 }; yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -1 }; yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreSymbols, s_expectedHalfToFullFormsComparison }; - yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreCase, s_expectedHalfToFullFormsComparison }; - yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreNonSpace, s_expectedHalfToFullFormsComparison }; - yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.None, s_expectedHalfToFullFormsComparison }; + yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreCase, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : s_expectedHalfToFullFormsComparison }; + yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreNonSpace, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : s_expectedHalfToFullFormsComparison }; + yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : s_expectedHalfToFullFormsComparison }; - yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.IgnoreKanaType, 0 }; yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.IgnoreCase, s_expectedHiraganaToKatakanaCompare }; - yield return new object[] { s_invariantCompare, "c", "C", CompareOptions.IgnoreKanaType, -1 }; // Spanish yield return new object[] { new CultureInfo("es-ES").CompareInfo, "llegar", "lugar", CompareOptions.None, -1 }; + if (PlatformDetection.IsNotHybridGlobalizationOnOSX) + { + yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", CompareOptions.IgnoreNonSpace, 0 }; + yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", CompareOptions.IgnoreCase, 0 }; + + yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.IgnoreKanaType, 0 }; + yield return new object[] { s_invariantCompare, "c", "C", CompareOptions.IgnoreKanaType, -1 }; + + yield return new object[] { s_invariantCompare, "Test's", "Tests", CompareOptions.IgnoreSymbols, 0 }; + yield return new object[] { s_invariantCompare, "Test's", "Tests", CompareOptions.StringSort, -1 }; + yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.IgnoreSymbols, 0 }; + yield return new object[] { s_invariantCompare, "\u00A2", "\uFFE0", CompareOptions.IgnoreSymbols, 0 }; + yield return new object[] { s_invariantCompare, "$", "&", CompareOptions.IgnoreSymbols, 0 }; + yield return new object[] { s_invariantCompare, "\uFF65", "\u30FB", CompareOptions.IgnoreSymbols, 0 }; + yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreSymbols, s_expectedHalfToFullFormsComparison }; + } // Zero-weight code points // In both NLS (Windows) and ICU the code point U+200C ZERO WIDTH NON-JOINER has a zero weight, // so it's compared as equal to the empty string. This means that we can't special-case GetHashCode("") @@ -352,7 +359,7 @@ public void SortKeyKanaTest(CompareInfo compareInfo, string string1, string stri SortKeyTest(compareInfo, string1, string2, options, expected); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalization))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] public void SortKeyTestNotSupported() { try @@ -395,7 +402,7 @@ public void SortKeyTestNotSupported() private static bool WindowsVersionHasTheCompareStringRegression => PlatformDetection.IsNlsGlobalization && CompareStringEx("", NORM_LINGUISTIC_CASING, "", 0, "\u200C", 1, IntPtr.Zero, IntPtr.Zero, 0) != 2; - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalization))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] [MemberData(nameof(SortKey_TestData))] public void SortKeyTest(CompareInfo compareInfo, string string1, string string2, CompareOptions options, int expectedSign) { @@ -405,7 +412,7 @@ public void SortKeyTest(CompareInfo compareInfo, string string1, string string2, Assert.Equal(expectedSign, Math.Sign(SortKey.Compare(sk1, sk2))); Assert.Equal(expectedSign == 0, sk1.Equals(sk2)); - if (!WindowsVersionHasTheCompareStringRegression) + if (!WindowsVersionHasTheCompareStringRegression && PlatformDetection.IsNotHybridGlobalizationOnOSX) { Assert.Equal(Math.Sign(compareInfo.Compare(string1, string2, options)), Math.Sign(SortKey.Compare(sk1, sk2))); } @@ -444,7 +451,7 @@ unsafe static void RunSpanSortKeyTest(CompareInfo compareInfo, ReadOnlySpan + + + + diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringGetHashCodeTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringGetHashCodeTests.cs index 33820345f65bd..b37ba0a5d051f 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringGetHashCodeTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringGetHashCodeTests.cs @@ -89,7 +89,8 @@ public static IEnumerable GetHashCodeOrdinalIgnoreCase_TestData() { yield return new object[] { "AaBbCcDdEeFfGgHh".Insert(i, "\u00E9" /* LATIN SMALL LETTER E WITH ACUTE */) }; yield return new object[] { "AaBbCcDdEeFfGgHh".Insert(i, "\u044D" /* CYRILLIC SMALL LETTER E */) }; - yield return new object[] { "AaBbCcDdEeFfGgHh".Insert(i, "\u0131" /* LATIN SMALL LETTER DOTLESS I */) }; + if (PlatformDetection.IsNotHybridGlobalizationOnOSX) + yield return new object[] { "AaBbCcDdEeFfGgHh".Insert(i, "\u0131" /* LATIN SMALL LETTER DOTLESS I */) }; } // Various texts copied from Microsoft's non-U.S. home pages, for further localization tests diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs index c5f876417d214..507c50dfda3ca 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs @@ -232,7 +232,7 @@ public static IEnumerable Contains_String_StringComparison_TestData() yield return new object[] { "Hello", "", StringComparison.CurrentCulture, true }; yield return new object[] { "Hello", "Ell" + SoftHyphen, StringComparison.CurrentCulture, false }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "Hello", "ell" + SoftHyphen, StringComparison.CurrentCulture, true }; // CurrentCultureIgnoreCase @@ -245,7 +245,7 @@ public static IEnumerable Contains_String_StringComparison_TestData() yield return new object[] { "", "hello", StringComparison.CurrentCultureIgnoreCase, false }; yield return new object[] { "Hello", "", StringComparison.CurrentCultureIgnoreCase, true }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) { yield return new object[] { "Hello", "ell" + SoftHyphen, StringComparison.CurrentCultureIgnoreCase, true }; yield return new object[] { "Hello", "Ell" + SoftHyphen, StringComparison.CurrentCultureIgnoreCase, true }; @@ -262,7 +262,7 @@ public static IEnumerable Contains_String_StringComparison_TestData() yield return new object[] { "Hello", "", StringComparison.InvariantCulture, true }; yield return new object[] { "Hello", "Ell" + SoftHyphen, StringComparison.InvariantCulture, false }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "Hello", "ell" + SoftHyphen, StringComparison.InvariantCulture, true }; // InvariantCultureIgnoreCase @@ -275,7 +275,7 @@ public static IEnumerable Contains_String_StringComparison_TestData() yield return new object[] { "", "hello", StringComparison.InvariantCultureIgnoreCase, false }; yield return new object[] { "Hello", "", StringComparison.InvariantCultureIgnoreCase, true }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) { yield return new object[] { "Hello", "ell" + SoftHyphen, StringComparison.InvariantCultureIgnoreCase, true }; yield return new object[] { "Hello", "Ell" + SoftHyphen, StringComparison.InvariantCultureIgnoreCase, true }; @@ -699,7 +699,7 @@ public static IEnumerable Replace_StringComparison_TestData() yield return new object[] { "abc", "b", "d", StringComparison.CurrentCulture, "adc" }; yield return new object[] { "abc", "b", null, StringComparison.CurrentCulture, "ac" }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "abc", "abc" + SoftHyphen, "def", StringComparison.CurrentCulture, "def" }; yield return new object[] { "abc", "abc", "def", StringComparison.CurrentCultureIgnoreCase, "def" }; @@ -709,7 +709,7 @@ public static IEnumerable Replace_StringComparison_TestData() yield return new object[] { "abc", "b", "d", StringComparison.CurrentCultureIgnoreCase, "adc" }; yield return new object[] { "abc", "b", null, StringComparison.CurrentCultureIgnoreCase, "ac" }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "abc", "abc" + SoftHyphen, "def", StringComparison.CurrentCultureIgnoreCase, "def" }; yield return new object[] { "abc", "abc", "def", StringComparison.Ordinal, "def" }; @@ -719,7 +719,7 @@ public static IEnumerable Replace_StringComparison_TestData() yield return new object[] { "abc", "b", "d", StringComparison.Ordinal, "adc" }; yield return new object[] { "abc", "b", null, StringComparison.Ordinal, "ac" }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "abc", "abc" + SoftHyphen, "def", StringComparison.Ordinal, "abc" }; yield return new object[] { "abc", "abc", "def", StringComparison.OrdinalIgnoreCase, "def" }; @@ -730,7 +730,7 @@ public static IEnumerable Replace_StringComparison_TestData() yield return new object[] { "abc", "b", null, StringComparison.OrdinalIgnoreCase, "ac" }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "abc", "abc" + SoftHyphen, "def", StringComparison.OrdinalIgnoreCase, "abc" }; yield return new object[] { "abc", "abc", "def", StringComparison.InvariantCulture, "def" }; @@ -741,7 +741,7 @@ public static IEnumerable Replace_StringComparison_TestData() yield return new object[] { "abc", "b", null, StringComparison.InvariantCulture, "ac" }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) yield return new object[] { "abc", "abc" + SoftHyphen, "def", StringComparison.InvariantCulture, "def" }; yield return new object[] { "abc", "abc", "def", StringComparison.InvariantCultureIgnoreCase, "def" }; @@ -752,7 +752,7 @@ public static IEnumerable Replace_StringComparison_TestData() yield return new object[] { "abc", "b", null, StringComparison.InvariantCultureIgnoreCase, "ac" }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) { yield return new object[] { "abc", "abc" + SoftHyphen, "def", StringComparison.InvariantCultureIgnoreCase, "def" }; @@ -821,7 +821,7 @@ public static IEnumerable Replace_StringComparisonCulture_TestData() yield return new object[] { "abc", "abc", "def", true, CultureInfo.InvariantCulture, "def" }; yield return new object[] { "abc", "ABC", "def", true, CultureInfo.InvariantCulture, "def" }; - if (PlatformDetection.IsNotInvariantGlobalization) + if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnOSX) { yield return new object[] { "abc", "abc" + SoftHyphen, "def", false, null, "def" }; yield return new object[] { "abc", "abc" + SoftHyphen, "def", true, null, "def" }; diff --git a/src/native/libs/System.Globalization.Native/entrypoints.c b/src/native/libs/System.Globalization.Native/entrypoints.c index 1f345991505fa..bc173b1c2c13e 100644 --- a/src/native/libs/System.Globalization.Native/entrypoints.c +++ b/src/native/libs/System.Globalization.Native/entrypoints.c @@ -74,6 +74,7 @@ static const Entry s_globalizationNative[] = DllImportEntry(GlobalizationNative_GetLocaleNameNative) DllImportEntry(GlobalizationNative_GetLocalesNative) DllImportEntry(GlobalizationNative_GetLocaleTimeFormatNative) + DllImportEntry(GlobalizationNative_GetSortKeyNative) DllImportEntry(GlobalizationNative_GetTimeZoneDisplayNameNative) DllImportEntry(GlobalizationNative_IndexOfNative) DllImportEntry(GlobalizationNative_IsNormalizedNative) diff --git a/src/native/libs/System.Globalization.Native/pal_collation.h b/src/native/libs/System.Globalization.Native/pal_collation.h index a8b44ba164f4a..197da249a0a04 100644 --- a/src/native/libs/System.Globalization.Native/pal_collation.h +++ b/src/native/libs/System.Globalization.Native/pal_collation.h @@ -98,6 +98,14 @@ PALEXPORT int32_t GlobalizationNative_EndsWithNative(const uint16_t* localeName, int32_t cwSuffixLength, const uint16_t* lpSource, int32_t cwSourceLength, - int32_t options); + int32_t options); + +PALEXPORT int32_t GlobalizationNative_GetSortKeyNative(const uint16_t* localeName, + int32_t lNameLength, + const UChar* lpStr, + int32_t cwStrLength, + uint8_t* sortKey, + int32_t cbSortKeyLength, + int32_t options); #endif diff --git a/src/native/libs/System.Globalization.Native/pal_collation.m b/src/native/libs/System.Globalization.Native/pal_collation.m index 0c7872725af2a..727024a65bbf8 100644 --- a/src/native/libs/System.Globalization.Native/pal_collation.m +++ b/src/native/libs/System.Globalization.Native/pal_collation.m @@ -295,4 +295,43 @@ int32_t GlobalizationNative_EndsWithNative(const uint16_t* localeName, int32_t l } } +int32_t GlobalizationNative_GetSortKeyNative(const uint16_t* localeName, int32_t lNameLength, const UChar* lpStr, int32_t cwStrLength, + uint8_t* sortKey, int32_t cbSortKeyLength, int32_t options) +{ + @autoreleasepool { + if (cwStrLength == 0) + { + if (sortKey != NULL) + sortKey[0] = '\0'; + return 1; + } + NSString *sourceString = [NSString stringWithCharacters: lpStr length: cwStrLength]; + NSString *sourceStringCleaned = RemoveWeightlessCharacters(sourceString).precomposedStringWithCanonicalMapping; + // If the string is empty after removing weightless characters, return 1 + if(sourceStringCleaned.length == 0) + { + if (sortKey != NULL) + sortKey[0] = '\0'; + return 1; + } + + NSLocale *locale = GetCurrentLocale(localeName, lNameLength); + NSStringCompareOptions comparisonOptions = options == 0 ? 0 : ConvertFromCompareOptionsToNSStringCompareOptions(options); + + // Generate a sort key for the original string based on the locale + NSString *transformedString = [sourceStringCleaned stringByFoldingWithOptions:comparisonOptions locale:locale]; + + NSUInteger transformedStringBytes = [transformedString lengthOfBytesUsingEncoding: NSUTF16StringEncoding]; + if (sortKey == NULL) + return (int32_t)transformedStringBytes; + NSRange range = NSMakeRange(0, [transformedString length]); + NSUInteger usedLength = 0; + BOOL result = [transformedString getBytes:sortKey maxLength:transformedStringBytes usedLength:&usedLength encoding:NSUTF16StringEncoding options:0 range:range remainingRange:NULL]; + if (result) + return (int32_t)usedLength; + + return 0; + } +} + #endif