diff --git a/docs/design/features/globalization-hybrid-mode.md b/docs/design/features/globalization-hybrid-mode.md index ec91448797cf5..fa70e222badff 100644 --- a/docs/design/features/globalization-hybrid-mode.md +++ b/docs/design/features/globalization-hybrid-mode.md @@ -379,7 +379,8 @@ The number of `CompareOptions` and `NSStringCompareOptions` combinations are lim - `IgnoreSymbols` is not supported because there is no equivalent in native api. Throws `PlatformNotSupportedException`. -- `IgnoreKanaType` is not supported because there is no equivalent in native api. Throws `PlatformNotSupportedException`. +- `IgnoreKanaType` is implemented using [`kCFStringTransformHiraganaKatakana`](https://developer.apple.com/documentation/corefoundation/kcfstringtransformhiraganakatakana?language=objc) then comparing strings. + - `None`: @@ -419,9 +420,7 @@ The number of `CompareOptions` and `NSStringCompareOptions` combinations are lim - All combinations that contain below `CompareOptions` always throw `PlatformNotSupportedException`: - `IgnoreSymbols`, - - `IgnoreKanaType`, + `IgnoreSymbols` ## String starts with / ends with diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.iOS.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.iOS.cs index 45ffb5ad31ec0..3ad9df0fcc25e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.iOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.iOS.cs @@ -80,7 +80,7 @@ private static void AssertComparisonSupported(CompareOptions options) } private const CompareOptions SupportedCompareOptions = CompareOptions.None | CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | - CompareOptions.IgnoreWidth | CompareOptions.StringSort; + CompareOptions.IgnoreWidth | CompareOptions.StringSort | CompareOptions.IgnoreKanaType; private static string GetPNSE(CompareOptions options) => SR.Format(SR.PlatformNotSupported_HybridGlobalizationWithCompareOptions, options); diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.Compare.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.Compare.cs index 846640c710dce..0a642b36c5694 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.Compare.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.Compare.cs @@ -45,28 +45,24 @@ public static IEnumerable Compare_TestData() CompareOptions validIgnoreKanaTypeOption = PlatformDetection.IsHybridGlobalizationOnBrowser ? CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase : CompareOptions.IgnoreKanaType; - // In HybridGlobalization, IgnoreKanaType is not supported on OSX - if (!PlatformDetection.IsHybridGlobalizationOnOSX) + yield return new object[] { s_invariantCompare, "\u3042", "\u30A2", validIgnoreKanaTypeOption, 0 }; + yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u30E3", validIgnoreKanaTypeOption, 0 }; + yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u3083", validIgnoreKanaTypeOption, 0 }; + yield return new object[] { s_invariantCompare, "\u304D \u3083", "\u30AD\u3083", validIgnoreKanaTypeOption, -1 }; + yield return new object[] { s_invariantCompare, "\u3044", "I", validIgnoreKanaTypeOption, 1 }; + yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u30D6\u30D9\u30DC", validIgnoreKanaTypeOption, 0 }; + yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\u30DC", validIgnoreKanaTypeOption, 0 }; + yield return new object[] { s_invariantCompare, "\u3060", "\u305F", validIgnoreKanaTypeOption, 1 }; + yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", validIgnoreKanaTypeOption, 0 }; + yield return new object[] { s_invariantCompare, "\u68EE\u9D0E\u5916", "\u68EE\u9DD7\u5916", validIgnoreKanaTypeOption, -1 }; + yield return new object[] { s_invariantCompare, "\u2019", "'", validIgnoreKanaTypeOption, 1 }; + yield return new object[] { s_invariantCompare, "", "'", validIgnoreKanaTypeOption, -1 }; + yield return new object[] { s_invariantCompare, "\u30FC", "\uFF70", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase, 0 }; + // PlatformDetection.IsHybridGlobalizationOnBrowser does not support IgnoreWidth + if (!PlatformDetection.IsHybridGlobalizationOnBrowser) { - yield return new object[] { s_invariantCompare, "\u3042", "\u30A2", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u30E3", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u3083", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u304D \u3083", "\u30AD\u3083", validIgnoreKanaTypeOption, -1 }; - yield return new object[] { s_invariantCompare, "\u3044", "I", validIgnoreKanaTypeOption, 1 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u30D6\u30D9\u30DC", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\u30DC", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u3060", "\u305F", validIgnoreKanaTypeOption, 1 }; - yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u68EE\u9D0E\u5916", "\u68EE\u9DD7\u5916", validIgnoreKanaTypeOption, -1 }; - yield return new object[] { s_invariantCompare, "\u2019", "'", validIgnoreKanaTypeOption, 1 }; - yield return new object[] { s_invariantCompare, "", "'", validIgnoreKanaTypeOption, -1 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\uFF70", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase, 0 }; - // PlatformDetection.IsHybridGlobalizationOnBrowser does not support IgnoreWidth - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - { - yield return new object[] { s_invariantCompare, "\u3042", "\uFF71", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "'\u3000'", "' '", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, 0 }; - } + yield return new object[] { s_invariantCompare, "\u3042", "\uFF71", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "'\u3000'", "' '", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : 0 }; } yield return new object[] { s_invariantCompare, "\u6FA4", "\u6CA2", CompareOptions.None, 1 }; @@ -264,12 +260,8 @@ public static IEnumerable Compare_TestData() // in HybridGlobalization on Browser IgnoreKanaType is supported only for "ja" var kanaComparison = PlatformDetection.IsHybridGlobalizationOnBrowser ? s_japaneseCompare : s_invariantCompare; - // In HybridGlobalization mode on OSX IgnoreKanaType is not supported - if (!PlatformDetection.IsHybridGlobalizationOnOSX) - { - yield return new object[] { kanaComparison, "\u3060", "\u30C0", CompareOptions.IgnoreKanaType, 0 }; - yield return new object[] { kanaComparison, "c", "C", CompareOptions.IgnoreKanaType, -1 }; - } + yield return new object[] { kanaComparison, "\u3060", "\u30C0", CompareOptions.IgnoreKanaType, 0 }; + yield return new object[] { kanaComparison, "c", "C", CompareOptions.IgnoreKanaType, -1 }; yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.IgnoreCase, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : s_expectedHiraganaToKatakanaCompare }; @@ -293,15 +285,12 @@ public static IEnumerable Compare_TestData() // Misc differences between platforms bool useNls = PlatformDetection.IsNlsGlobalization; - if (!PlatformDetection.IsHybridGlobalizationOnOSX) - { - var japaneseCmp = PlatformDetection.IsHybridGlobalizationOnBrowser ? - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase : - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; + var japaneseCmp = PlatformDetection.IsHybridGlobalizationOnBrowser ? + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase : + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; - yield return new object[] { s_invariantCompare, "\u3042", "\u30A1", japaneseCmp, useNls ? 1: 0 }; - yield return new object[] { s_invariantCompare, "'\u3000'", "''", japaneseCmp, useNls ? 1 : -1 }; - } + yield return new object[] { s_invariantCompare, "\u3042", "\u30A1", japaneseCmp, useNls || PlatformDetection.IsHybridGlobalizationOnOSX ? 1: 0 }; + yield return new object[] { s_invariantCompare, "'\u3000'", "''", japaneseCmp, useNls ? 1 : -1 }; yield return new object[] { s_invariantCompare, "\u30BF", "\uFF80", CompareOptions.None, useNls ? 1 : -1 }; yield return new object[] { s_invariantCompare, "'\u3000'", "''", CompareOptions.None, useNls ? 1 : -1 }; @@ -339,7 +328,6 @@ public void CompareWithUnassignedChars() } [ConditionalTheory(nameof(IsNotWindowsKanaRegressedVersion))] - [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS | TestPlatforms.OSX, "Not supported on Browser, iOS, MacCatalyst, or tvOS.")] [MemberData(nameof(Compare_Kana_TestData))] public void CompareWithKana(CompareInfo compareInfo, string string1, string string2, CompareOptions options, int expected) { @@ -525,7 +513,6 @@ public void Compare_Invalid() } [Fact] - [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS | TestPlatforms.OSX, "Not supported on Browser, iOS, MacCatalyst, or tvOS.")] public void TestIgnoreKanaAndWidthCases() { for (char c = '\uFF41'; c <= '\uFF5A'; c++) @@ -536,7 +523,7 @@ public void TestIgnoreKanaAndWidthCases() // Edge case of the Ignore Width. Assert.False(string.Compare("\u3162\u3163", "\uFFDB\uFFDC", CultureInfo.InvariantCulture, CompareOptions.None) == 0, $"Expect '0x3162 0x3163' != '0xFFDB 0xFFDC'"); - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) + if (!PlatformDetection.IsHybridGlobalization) Assert.True(string.Compare("\u3162\u3163", "\uFFDB\uFFDC", CultureInfo.InvariantCulture, CompareOptions.IgnoreWidth) == 0, "Expect '0x3162 0x3163' == '0xFFDB 0xFFDC'"); const char hiraganaStart = '\u3041'; @@ -544,7 +531,8 @@ public void TestIgnoreKanaAndWidthCases() const int hiraganaToKatakanaOffset = 0x30a1 - 0x3041; // in HybridGlobalization on Browser IgnoreKanaType is supported only for "ja-JP" - CultureInfo ignoreKanaTypeTestedCulture = PlatformDetection.IsHybridGlobalizationOnBrowser ? new CultureInfo("ja-JP") : CultureInfo.InvariantCulture; + // in HybridGlobalization on OSX behavior is different for CultureInfo.InvariantCulture + CultureInfo ignoreKanaTypeTestedCulture = PlatformDetection.IsHybridGlobalization ? new CultureInfo("ja-JP") : CultureInfo.InvariantCulture; for (Char hiraganaChar = hiraganaStart; hiraganaChar <= hiraganaEnd; hiraganaChar++) { @@ -572,6 +560,24 @@ public static IEnumerable Compare_HiraganaAndKatakana_TestData() CompareOptions.IgnoreKanaType | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, CompareOptions.Ordinal, CompareOptions.OrdinalIgnoreCase, + } : PlatformDetection.IsHybridGlobalizationOnOSX ? + new[] { + CompareOptions.None, + CompareOptions.IgnoreCase, + CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreKanaType, + CompareOptions.IgnoreWidth, + CompareOptions.Ordinal, + CompareOptions.OrdinalIgnoreCase, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, + CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, } : new[] { CompareOptions.None, @@ -621,7 +627,6 @@ public static IEnumerable Compare_HiraganaAndKatakana_TestData() } [Theory] - [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS | TestPlatforms.OSX, "Not supported on Browser, iOS, MacCatalyst, or tvOS.")] [MemberData(nameof(Compare_HiraganaAndKatakana_TestData))] public void TestHiraganaAndKatakana(CompareOptions[] optionsPositive, CompareOptions[] optionsNegative) { 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 8ca1892cde0c8..72d03ec00e3c8 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 @@ -114,75 +114,72 @@ public static IEnumerable SortKey_Kana_TestData() public static IEnumerable SortKey_TestData() { - if (PlatformDetection.IsNotHybridGlobalizationOnOSX) + 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, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; + yield return new object[] { s_invariantCompare, "\u2019", "'", ignoreKanaIgnoreWidthIgnoreCase, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 1 }; + yield return new object[] { s_invariantCompare, "", "'", ignoreKanaIgnoreWidthIgnoreCase, -1 }; + yield return new object[] { s_invariantCompare, "\u4E00", "\uFF11", ignoreKanaIgnoreWidthIgnoreCase, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 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, PlatformDetection.IsHybridGlobalizationOnOSX ? -1 : 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) { - 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 }; - } + // 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 }; @@ -287,14 +284,13 @@ public static IEnumerable SortKey_TestData() // Spanish yield return new object[] { new CultureInfo("es-ES").CompareInfo, "llegar", "lugar", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.IgnoreKanaType, 0 }; + yield return new object[] { s_invariantCompare, "c", "C", CompareOptions.IgnoreKanaType, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : -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 }; @@ -352,7 +348,7 @@ public static void LcidTest(string cultureName, int lcid) Assert.Equal(lcid, ci.LCID); } - [ConditionalTheory(typeof(CompareInfoTests), nameof(IsNotWindowsKanaRegressedVersionAndNotHybridGlobalization))] + [ConditionalTheory(typeof(CompareInfoTests), nameof(IsNotWindowsKanaRegressedVersionAndNotHybridGlobalizationOnWasm))] [MemberData(nameof(SortKey_Kana_TestData))] public void SortKeyKanaTest(CompareInfo compareInfo, string string1, string string2, CompareOptions options, int expected) { diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTestsBase.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTestsBase.cs index 3397b16e16918..01983c6865145 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTestsBase.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTestsBase.cs @@ -45,6 +45,5 @@ protected static bool IsNotWindowsKanaRegressedVersion() => !PlatformDetection.I s_invariantCompare.Compare("\u3060", "\uFF80\uFF9E", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase) == 0; protected static bool IsNotWindowsKanaRegressedVersionAndNotHybridGlobalizationOnWasm() => !PlatformDetection.IsHybridGlobalizationOnBrowser && IsNotWindowsKanaRegressedVersion(); - protected static bool IsNotWindowsKanaRegressedVersionAndNotHybridGlobalization() => IsNotWindowsKanaRegressedVersion() && PlatformDetection.IsNotHybridGlobalization; } } diff --git a/src/native/libs/System.Globalization.Native/pal_collation.m b/src/native/libs/System.Globalization.Native/pal_collation.m index 727024a65bbf8..6326b267c582c 100644 --- a/src/native/libs/System.Globalization.Native/pal_collation.m +++ b/src/native/libs/System.Globalization.Native/pal_collation.m @@ -19,6 +19,7 @@ None = 0, IgnoreCase = 1, IgnoreNonSpace = 2, + IgnoreKanaType = 8, IgnoreWidth = 16, StringSort = 536870912, } CompareOptions; @@ -45,15 +46,19 @@ return currentLocale; } -static NSStringCompareOptions ConvertFromCompareOptionsToNSStringCompareOptions(int32_t comparisonOptions) +static bool IsComparisonOptionSupported(int32_t comparisonOptions) +{ + int32_t supportedOptions = None | IgnoreCase | IgnoreNonSpace | IgnoreWidth | StringSort | IgnoreKanaType; + if ((comparisonOptions | supportedOptions) != supportedOptions) + return false; + return true; +} + +static NSStringCompareOptions ConvertFromCompareOptionsToNSStringCompareOptions(int32_t comparisonOptions, bool isLiteralSearchSupported) { - int32_t supportedOptions = None | IgnoreCase | IgnoreNonSpace | IgnoreWidth | StringSort; // To achieve an equivalent search behavior to the default in ICU, // NSLiteralSearch is employed as the default search option. - NSStringCompareOptions options = NSLiteralSearch; - - if ((comparisonOptions | supportedOptions) != supportedOptions) - return 0; + NSStringCompareOptions options = isLiteralSearchSupported ? NSLiteralSearch : 0; if (comparisonOptions & IgnoreCase) options |= NSCaseInsensitiveSearch; @@ -67,32 +72,49 @@ static NSStringCompareOptions ConvertFromCompareOptionsToNSStringCompareOptions( return options; } +NSString *ConvertToKatakana(NSString *input) +{ + NSMutableString *mutableString = [input mutableCopy]; + CFStringTransform((__bridge CFMutableStringRef)mutableString, NULL, kCFStringTransformHiraganaKatakana, false); + return mutableString; +} + /* Function: CompareString */ int32_t GlobalizationNative_CompareStringNative(const uint16_t* localeName, int32_t lNameLength, const uint16_t* lpSource, int32_t cwSourceLength, const uint16_t* lpTarget, int32_t cwTargetLength, int32_t comparisonOptions) -{ +{ @autoreleasepool { + if (!IsComparisonOptionSupported(comparisonOptions)) + return ERROR_COMPARISON_OPTIONS_NOT_FOUND; NSLocale *currentLocale = GetCurrentLocale(localeName, lNameLength); NSString *sourceString = [NSString stringWithCharacters: lpSource length: cwSourceLength]; NSString *sourceStrPrecomposed = sourceString.precomposedStringWithCanonicalMapping; NSString *targetString = [NSString stringWithCharacters: lpTarget length: cwTargetLength]; NSString *targetStrPrecomposed = targetString.precomposedStringWithCanonicalMapping; - NSRange comparisonRange = NSMakeRange(0, sourceStrPrecomposed.length); - NSStringCompareOptions options = ConvertFromCompareOptionsToNSStringCompareOptions(comparisonOptions); - - // in case mapping is not found - if (options == 0) - return ERROR_COMPARISON_OPTIONS_NOT_FOUND; + if (comparisonOptions & IgnoreKanaType) + { + sourceStrPrecomposed = ConvertToKatakana(sourceStrPrecomposed); + targetStrPrecomposed = ConvertToKatakana(targetStrPrecomposed); + } + if (comparisonOptions != 0 && comparisonOptions != StringSort) + { + NSStringCompareOptions options = ConvertFromCompareOptionsToNSStringCompareOptions(comparisonOptions, false); + sourceStrPrecomposed = [sourceStrPrecomposed stringByFoldingWithOptions:options locale:currentLocale]; + targetStrPrecomposed = [targetStrPrecomposed stringByFoldingWithOptions:options locale:currentLocale]; + } + + NSStringCompareOptions options = ConvertFromCompareOptionsToNSStringCompareOptions(comparisonOptions, true); + NSRange comparisonRange = NSMakeRange(0, sourceStrPrecomposed.length); return [sourceStrPrecomposed compare:targetStrPrecomposed - options:options - range:comparisonRange - locale:currentLocale]; + options:options + range:comparisonRange + locale:currentLocale]; } } @@ -131,24 +153,26 @@ Range GlobalizationNative_IndexOfNative(const uint16_t* localeName, int32_t lNam { assert(cwTargetLength >= 0); Range result = {ERROR_INDEX_NOT_FOUND, 0}; - NSStringCompareOptions options = ConvertFromCompareOptionsToNSStringCompareOptions(comparisonOptions); - - // in case mapping is not found - if (options == 0) + if (!IsComparisonOptionSupported(comparisonOptions)) { result.location = ERROR_COMPARISON_OPTIONS_NOT_FOUND; return result; } - + NSStringCompareOptions options = ConvertFromCompareOptionsToNSStringCompareOptions(comparisonOptions, true); NSString *searchString = [NSString stringWithCharacters: lpTarget length: cwTargetLength]; NSString *searchStrCleaned = RemoveWeightlessCharacters(searchString); NSString *sourceString = [NSString stringWithCharacters: lpSource length: cwSourceLength]; NSString *sourceStrCleaned = RemoveWeightlessCharacters(sourceString); + if (comparisonOptions & IgnoreKanaType) + { + sourceStrCleaned = ConvertToKatakana(sourceStrCleaned); + searchStrCleaned = ConvertToKatakana(searchStrCleaned); + } if (sourceStrCleaned.length == 0 || searchStrCleaned.length == 0) { - result.location = fromBeginning ? 0 : sourceString.length; - return result; + result.location = fromBeginning ? 0 : sourceString.length; + return result; } NSLocale *currentLocale = GetCurrentLocale(localeName, lNameLength); @@ -243,17 +267,19 @@ int32_t GlobalizationNative_StartsWithNative(const uint16_t* localeName, int32_t { @autoreleasepool { - NSStringCompareOptions options = ConvertFromCompareOptionsToNSStringCompareOptions(comparisonOptions); - - // in case mapping is not found - if (options == 0) + if (!IsComparisonOptionSupported(comparisonOptions)) return ERROR_COMPARISON_OPTIONS_NOT_FOUND; - + NSStringCompareOptions options = ConvertFromCompareOptionsToNSStringCompareOptions(comparisonOptions, true); NSLocale *currentLocale = GetCurrentLocale(localeName, lNameLength); NSString *prefixString = [NSString stringWithCharacters: lpPrefix length: cwPrefixLength]; NSString *prefixStrComposed = RemoveWeightlessCharacters(prefixString.precomposedStringWithCanonicalMapping); NSString *sourceString = [NSString stringWithCharacters: lpSource length: cwSourceLength]; NSString *sourceStrComposed = RemoveWeightlessCharacters(sourceString.precomposedStringWithCanonicalMapping); + if (comparisonOptions & IgnoreKanaType) + { + prefixStrComposed = ConvertToKatakana(prefixStrComposed); + sourceStrComposed = ConvertToKatakana(sourceStrComposed); + } NSRange sourceRange = NSMakeRange(0, prefixStrComposed.length > sourceStrComposed.length ? sourceStrComposed.length : prefixStrComposed.length); @@ -273,17 +299,19 @@ int32_t GlobalizationNative_EndsWithNative(const uint16_t* localeName, int32_t l { @autoreleasepool { - NSStringCompareOptions options = ConvertFromCompareOptionsToNSStringCompareOptions(comparisonOptions); - - // in case mapping is not found - if (options == 0) + if (!IsComparisonOptionSupported(comparisonOptions)) return ERROR_COMPARISON_OPTIONS_NOT_FOUND; - + NSStringCompareOptions options = ConvertFromCompareOptionsToNSStringCompareOptions(comparisonOptions, true); NSLocale *currentLocale = GetCurrentLocale(localeName, lNameLength); NSString *suffixString = [NSString stringWithCharacters: lpSuffix length: cwSuffixLength]; NSString *suffixStrComposed = RemoveWeightlessCharacters(suffixString.precomposedStringWithCanonicalMapping); NSString *sourceString = [NSString stringWithCharacters: lpSource length: cwSourceLength]; NSString *sourceStrComposed = RemoveWeightlessCharacters(sourceString.precomposedStringWithCanonicalMapping); + if (comparisonOptions & IgnoreKanaType) + { + suffixStrComposed = ConvertToKatakana(suffixStrComposed); + sourceStrComposed = ConvertToKatakana(sourceStrComposed); + } int32_t startIndex = suffixStrComposed.length > sourceStrComposed.length ? 0 : sourceStrComposed.length - suffixStrComposed.length; NSRange sourceRange = NSMakeRange(startIndex, sourceStrComposed.length - startIndex); @@ -305,7 +333,13 @@ int32_t GlobalizationNative_GetSortKeyNative(const uint16_t* localeName, int32_t sortKey[0] = '\0'; return 1; } + if (!IsComparisonOptionSupported(options)) + return 0; NSString *sourceString = [NSString stringWithCharacters: lpStr length: cwStrLength]; + if (options & IgnoreKanaType) + { + sourceString = ConvertToKatakana(sourceString); + } NSString *sourceStringCleaned = RemoveWeightlessCharacters(sourceString).precomposedStringWithCanonicalMapping; // If the string is empty after removing weightless characters, return 1 if(sourceStringCleaned.length == 0) @@ -316,7 +350,7 @@ int32_t GlobalizationNative_GetSortKeyNative(const uint16_t* localeName, int32_t } NSLocale *locale = GetCurrentLocale(localeName, lNameLength); - NSStringCompareOptions comparisonOptions = options == 0 ? 0 : ConvertFromCompareOptionsToNSStringCompareOptions(options); + NSStringCompareOptions comparisonOptions = options == 0 ? 0 : ConvertFromCompareOptionsToNSStringCompareOptions(options, false); // Generate a sort key for the original string based on the locale NSString *transformedString = [sourceStringCleaned stringByFoldingWithOptions:comparisonOptions locale:locale];