From f345a70611c2a061d9913a6ddf89f20ca486c1f1 Mon Sep 17 00:00:00 2001 From: Meri Khamoyan <96171496+mkhamoyan@users.noreply.github.com> Date: Wed, 24 May 2023 15:08:56 +0400 Subject: [PATCH] [OSX] HybridGlobalization implement compare native function (#85965) Implemented CompareStringNative for OSX platforms Added changes done by @ilonatommy in #86305 --- docs/design/features/hybrid-globalization.md | 62 +++++++ .../Common/src/Interop/Interop.Collation.OSX.cs | 16 ++ .../TestUtilities/System/PlatformDetection.cs | 1 + .../tests/CompareInfo/CompareInfoTests.Compare.cs | 206 ++++++++++++--------- .../Hybrid/System.Globalization.IOS.Tests.csproj | 2 + .../src/Resources/Strings.resx | 4 +- .../src/System.Private.CoreLib.Shared.projitems | 4 + .../src/System/Globalization/CompareInfo.OSX.cs | 47 +++++ .../src/System/Globalization/CompareInfo.cs | 3 + src/mono/mono/mini/CMakeLists.txt | 3 +- .../System.Globalization.Native/CMakeLists.txt | 2 +- .../libs/System.Globalization.Native/entrypoints.c | 1 + .../System.Globalization.Native/pal_collation.h | 10 + .../System.Globalization.Native/pal_collation.m | 77 ++++++++ 14 files changed, 345 insertions(+), 93 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Interop.Collation.OSX.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.OSX.cs create mode 100644 src/native/libs/System.Globalization.Native/pal_collation.m diff --git a/docs/design/features/hybrid-globalization.md b/docs/design/features/hybrid-globalization.md index d9f7e30..916bada 100644 --- a/docs/design/features/hybrid-globalization.md +++ b/docs/design/features/hybrid-globalization.md @@ -245,3 +245,65 @@ Using `IgnoreNonSpace` for these two with `HybridGlobalization` off, also return ``` C# new CultureInfo("de-DE").CompareInfo.IndexOf("strasse", "stra\u00DFe", 0, CompareOptions.IgnoreNonSpace); // 0 or -1 ``` + + +### OSX + +For OSX platforms we are using native apis instead of ICU data. + +**String comparison** + +Affected public APIs: +- CompareInfo.Compare, +- String.Compare, +- String.Equals. + +The number of `CompareOptions` and `NSStringCompareOptions` combinations are limited. Originally supported combinations can be found [here for CompareOptions](https://learn.microsoft.com/dotnet/api/system.globalization.compareoptions) and [here for NSStringCompareOptions](https://developer.apple.com/documentation/foundation/nsstringcompareoptions). + +- `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`. + +- `None`: + +`CompareOptions.None` is mapped to `NSStringCompareOptions.NSLiteralSearch` + +There are some behaviour changes. Below are examples of such cases. + +| **character 1** | **character 2** | **CompareOptions** | **hybrid globalization** | **icu** | **comments** | +|:---------------:|:---------------:|--------------------|:------------------------:|:-------:|:-------------------------------------------------------:| +| `\u3042` あ | `\u30A1` ァ | None | 1 | -1 | hiragana and katakana characters are ordered differently compared to ICU | +| `\u304D\u3083` きゃ | `\u30AD\u30E3` キャ | None | 1 | -1 | hiragana and katakana characters are ordered differently compared to ICU | +| `\u304D\u3083` きゃ | `\u30AD\u3083` キゃ | None | 1 | -1 | hiragana and katakana characters are ordered differently compared to ICU | +| `\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C` ばびブベぼ | `\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E` バビぶベボ | None | 1 | -1 | hiragana and katakana characters are ordered differently compared to ICU | +| `\u3060` だ | `\u30C0` ダ | None | 1 | -1 | hiragana and katakana characters are ordered differently compared to ICU | +| `\u00C0` À | `A\u0300` À | None | 1 | 0 | This is not same character for native api | + +- `StringSort` : + +`CompareOptions.StringSort` is mapped to `NSStringCompareOptions.NSLiteralSearch` .ICU's default is to use "StringSort", i.e. nonalphanumeric symbols come before alphanumeric. That is how works also `NSLiteralSearch`. + +- `IgnoreCase`: + +`CompareOptions.IgnoreCase` is mapped to `NSStringCompareOptions.NSCaseInsensitiveSearch | NSStringCompareOptions.NSLiteralSearch` + +There are some behaviour changes. Below are examples of such cases. + +| **character 1** | **character 2** | **CompareOptions** | **hybrid globalization** | **icu** | **comments** | +|:---------------:|:---------------:|--------------------|:------------------------:|:-------:|:-------------------------------------------------------:| +| `\u3060` だ | `\u30C0` ダ | IgnoreCase | 1 | -1 | hiragana and katakana characters are ordered differently compared to ICU | +| `\u00C0` À | `a\u0300` à | IgnoreCase | 1 | 0 | This is related to above mentioned case under `CompareOptions.None` i.e. `\u00C0` À != À `A\u0300` | + +- `IgnoreNonSpace`: + +`CompareOptions.IgnoreNonSpace` is mapped to `NSStringCompareOptions.NSDiacriticInsensitiveSearch | NSStringCompareOptions.NSLiteralSearch` + +- `IgnoreWidth`: + +`CompareOptions.IgnoreWidth` is mapped to `NSStringCompareOptions.NSWidthInsensitiveSearch | NSStringCompareOptions.NSLiteralSearch` + +- All combinations that contain below `CompareOptions` always throw `PlatformNotSupportedException`: + +`IgnoreSymbols`, + +`IgnoreKanaType`, diff --git a/src/libraries/Common/src/Interop/Interop.Collation.OSX.cs b/src/libraries/Common/src/Interop/Interop.Collation.OSX.cs new file mode 100644 index 0000000..d9e5022 --- /dev/null +++ b/src/libraries/Common/src/Interop/Interop.Collation.OSX.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Globalization + { + [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_CompareStringNative", StringMarshalling = StringMarshalling.Utf16)] + internal static unsafe partial int CompareStringNative(string localeName, int lNameLen, char* lpStr1, int cwStr1Len, char* lpStr2, int cwStr2Len, CompareOptions options); + } +} diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index e26bbb9..68ca799 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -356,6 +356,7 @@ namespace System public static bool IsInvariantGlobalization => m_isInvariant.Value; public static bool IsHybridGlobalizationOnBrowser => m_isHybrid.Value && IsBrowser; + public static bool IsHybridGlobalizationOnOSX => m_isHybrid.Value && (IsOSX || IsMacCatalyst || IsiOS || IstvOS); public static bool IsNotHybridGlobalizationOnBrowser => !IsHybridGlobalizationOnBrowser; public static bool IsNotInvariantGlobalization => !IsInvariantGlobalization; public static bool IsIcuGlobalization => ICUVersion > new Version(0, 0, 0, 0); diff --git a/src/libraries/System.Globalization/tests/CompareInfo/CompareInfoTests.Compare.cs b/src/libraries/System.Globalization/tests/CompareInfo/CompareInfoTests.Compare.cs index c7c24b9..eb4d7e9 100644 --- a/src/libraries/System.Globalization/tests/CompareInfo/CompareInfoTests.Compare.cs +++ b/src/libraries/System.Globalization/tests/CompareInfo/CompareInfoTests.Compare.cs @@ -41,78 +41,79 @@ namespace System.Globalization.Tests public static IEnumerable Compare_TestData() { + // PlatformDetection.IsHybridGlobalizationOnBrowser does not support IgnoreKanaType alone, it needs to be e.g. with IgnoreCase + 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", "\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, "\u6FA4", "\u6CA2", CompareOptions.None, 1 }; + yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u3079\uFF8E\uFF9E", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D6", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "\u3071\u3074\u30D7\u307A", "\uFF8B\uFF9F\uFF8C\uFF9F", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u3070\uFF8E\uFF9E\u30D6", CompareOptions.None, 1 }; + yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C\u3079\u307C", "\u3079\uFF8E\uFF9E", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "\u3070\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D6", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "ABDDE", "D", CompareOptions.None, -1 }; + yield return new object[] { s_invariantCompare, "\u2019\u2019\u2019\u2019", "''''", CompareOptions.None, 1 }; + yield return new object[] { s_invariantCompare, "\u4E00", "\uFF11", CompareOptions.None, 1 }; + yield return new object[] { s_invariantCompare, "\u2160", "\uFF11", CompareOptions.None, 1 }; + 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, "\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, "\u68EE\u9DD7\u5916", "\u68EE\u9DD7\u5916", CompareOptions.IgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "a", "A", CompareOptions.IgnoreCase, 0 }; // PlatformDetection.IsHybridGlobalizationOnBrowser does not support IgnoreWidth - CompareOptions ignoredOptions = PlatformDetection.IsHybridGlobalizationOnBrowser ? - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase : - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; - - yield return new object[] { s_invariantCompare, "\u3042", "\u30A2", ignoredOptions, 0 }; - yield return new object[] { s_invariantCompare, "\u3042", "\uFF71", ignoredOptions, 0 }; - - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u30E3", ignoredOptions, 0 }; - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u3083", ignoredOptions, 0 }; - yield return new object[] { s_invariantCompare, "\u304D \u3083", "\u30AD\u3083", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "\u3044", "I", ignoredOptions, 1 }; - - yield return new object[] { s_invariantCompare, "a", "A", ignoredOptions, 0 }; - yield return new object[] { s_invariantCompare, "a", "\uFF41", ignoredOptions, 0 }; // known exception for hg: should be -1 - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23\uFF24\uFF25", ignoredOptions, 0 }; // as above - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23D\uFF25", ignoredOptions, 0 }; // as above - yield return new object[] { s_invariantCompare, "ABCDE", "a\uFF22\uFF23D\uFF25", ignoredOptions, 0 }; // as above - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF41\uFF42\uFF23D\uFF25", ignoredOptions, 0 }; // as above - - yield return new object[] { s_invariantCompare, "\u6FA4", "\u6CA2", ignoredOptions, 1 }; - - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u30D6\u30D9\u30DC", ignoredOptions, 0 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\u30DC", ignoredOptions, 0 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u3079\uFF8E\uFF9E", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D6", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "\u3071\u3074\u30D7\u307A", "\uFF8B\uFF9F\uFF8C\uFF9F", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u3070\uFF8E\uFF9E\u30D6", ignoredOptions, 1 }; // known exception for hg: should be -1 - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C\u3079\u307C", "\u3079\uFF8E\uFF9E", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D6", ignoredOptions, -1 }; - - yield return new object[] { s_invariantCompare, "ABDDE", "D", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF43D", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "ABCDE", "c", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "\u3060", "\u305F", ignoredOptions, 1 }; - yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", ignoredOptions, 0 }; - - yield return new object[] { s_invariantCompare, "\u30BF", "\uFF80", ignoredOptions, 0 }; // known exception for hg: should be -1 - - yield return new object[] { s_invariantCompare, "\u68EE\u9D0E\u5916", "\u68EE\u9DD7\u5916", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "\u68EE\u9DD7\u5916", "\u68EE\u9DD7\u5916", ignoredOptions, 0 }; - yield return new object[] { s_invariantCompare, "\u2019\u2019\u2019\u2019", "''''", ignoredOptions, 1 }; - yield return new object[] { s_invariantCompare, "\u2019", "'", ignoredOptions, 1 }; - yield return new object[] { s_invariantCompare, "", "'", ignoredOptions, -1 }; - yield return new object[] { s_invariantCompare, "\u4E00", "\uFF11", ignoredOptions, 1 }; - yield return new object[] { s_invariantCompare, "\u2160", "\uFF11", ignoredOptions, 1 }; - - yield return new object[] { s_invariantCompare, "0", "\uFF10", ignoredOptions, 0 }; // known exception for hg: should be -1 - yield return new object[] { s_invariantCompare, "10", "1\uFF10", ignoredOptions, 0 }; // as above - yield return new object[] { s_invariantCompare, "9999\uFF1910", "1\uFF10", ignoredOptions, 1 }; - yield return new object[] { s_invariantCompare, "9999\uFF191010", "1\uFF10", ignoredOptions, 1 }; - - yield return new object[] { s_invariantCompare, "'\u3000'", "' '", ignoredOptions, 0 }; - yield return new object[] { s_invariantCompare, "\uFF1B", ";", ignoredOptions, 0 }; // known exception for hg: should be 1 - yield return new object[] { s_invariantCompare, "\uFF08", "(", ignoredOptions, 0 }; // known exception for hg: should be 1 - yield return new object[] { s_invariantCompare, "\u30FC", "\uFF70", ignoredOptions, 0 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\uFF0D", ignoredOptions, 1 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\u30FC", ignoredOptions, 0 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\u2015", ignoredOptions, 1 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\u2010", ignoredOptions, 1 }; - - yield return new object[] { s_invariantCompare, "/", "\uFF0F", ignoredOptions, 0 }; // known exception for hg: should be -1 - yield return new object[] { s_invariantCompare, "'", "\uFF07", ignoredOptions, 0 }; // as above - yield return new object[] { s_invariantCompare, "\"", "\uFF02", ignoredOptions, 0 }; // as above - - yield return new object[] { s_invariantCompare, "\u3042", "\u30A1", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; + if (!PlatformDetection.IsHybridGlobalizationOnBrowser) + { + yield return new object[] { s_invariantCompare, "a", "\uFF41", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23\uFF24\uFF25", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23D\uFF25", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "a\uFF22\uFF23D\uFF25", CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF41\uFF42\uFF23D\uFF25", CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF43D", CompareOptions.IgnoreWidth, -1 }; + yield return new object[] { s_invariantCompare, "ABCDE", "c", CompareOptions.IgnoreWidth, -1 }; + yield return new object[] { s_invariantCompare, "\u30BF", "\uFF80", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "0", "\uFF10", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "10", "1\uFF10", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "\uFF1B", ";", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "\uFF08", "(", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "/", "\uFF0F", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "'", "\uFF07", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "\"", "\uFF02", CompareOptions.IgnoreWidth, 0 }; + } + yield return new object[] { s_invariantCompare, "\u3042", "\u30A1", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : s_expectedHiraganaToKatakanaCompare }; yield return new object[] { s_invariantCompare, "\u3042", "\u30A2", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; yield return new object[] { s_invariantCompare, "\u3042", "\uFF71", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u30E3", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u3083", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; + yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u30E3", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : s_expectedHiraganaToKatakanaCompare }; + yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u3083", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : 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 }; @@ -126,7 +127,7 @@ namespace System.Globalization.Tests yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u30D6\u30D9\u30DC", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\u30DC", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; + yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : s_expectedHiraganaToKatakanaCompare }; yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", CompareOptions.None, -1 }; yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u3079\uFF8E\uFF9E", CompareOptions.None, -1 }; @@ -141,7 +142,7 @@ namespace System.Globalization.Tests yield return new object[] { s_invariantCompare, "ABCDE", "\uFF43D", CompareOptions.None, -1 }; yield return new object[] { s_invariantCompare, "ABCDE", "c", CompareOptions.None, -1 }; yield return new object[] { s_invariantCompare, "\u3060", "\u305F", CompareOptions.None, 1 }; - yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; + yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : s_expectedHiraganaToKatakanaCompare }; 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 }; @@ -191,16 +192,20 @@ namespace System.Globalization.Tests yield return new object[] { s_invariantCompare, "i", "\u0130", CompareOptions.None, -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, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : 0 }; yield return new object[] { s_invariantCompare, "\u00C0", "A\u0300", CompareOptions.Ordinal, 1 }; yield return new object[] { s_invariantCompare, "\u00C0", "a\u0300", CompareOptions.None, 1 }; - yield return new object[] { s_invariantCompare, "\u00C0", "a\u0300", CompareOptions.IgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u00C0", "a\u0300", CompareOptions.IgnoreCase, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : 0 }; yield return new object[] { s_invariantCompare, "\u00C0", "a\u0300", CompareOptions.Ordinal, 1 }; yield return new object[] { s_invariantCompare, "\u00C0", "a\u0300", CompareOptions.OrdinalIgnoreCase, 1 }; yield return new object[] { s_invariantCompare, "FooBar", "Foo\u0400Bar", CompareOptions.Ordinal, -1 }; yield return new object[] { s_invariantCompare, "FooBA\u0300R", "FooB\u00C0R", supportedIgnoreNonSpaceOption, 0 }; - yield return new object[] { s_invariantCompare, "Test's", "Tests", CompareOptions.IgnoreSymbols, 0 }; + // In HybridGlobalization on OSX IgnoreSymbols is not supported + if (!PlatformDetection.IsHybridGlobalizationOnOSX) + { + 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, null, "Tests", CompareOptions.None, -1 }; @@ -222,14 +227,19 @@ namespace System.Globalization.Tests yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", CompareOptions.IgnoreCase, PlatformDetection.IsHybridGlobalizationOnBrowser ? 1 : 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 }; - // some symbols e.g. currencies are not ignored correctly in HybridGlobalization - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) + + // In HybridGlobalization mode on OSX IgnoreSymbols is not supported + if (!PlatformDetection.IsHybridGlobalizationOnOSX) { - 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, "\u0021", "\uFF01", CompareOptions.IgnoreSymbols, 0 }; + yield return new object[] { s_invariantCompare, "\uFF65", "\u30FB", CompareOptions.IgnoreSymbols, 0 }; + // some symbols e.g. currencies are not ignored correctly in HybridGlobalization + if (!PlatformDetection.IsHybridGlobalizationOnBrowser) + { + 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.None, -1 }; if (!PlatformDetection.IsHybridGlobalizationOnBrowser) @@ -239,7 +249,12 @@ namespace System.Globalization.Tests yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreWidth, 0 }; } - yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreSymbols, s_expectedHalfToFullFormsComparison }; + // In HybridGlobalization mode on OSX IgnoreSymbols is not supported + if(!PlatformDetection.IsHybridGlobalizationOnOSX) + { + yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreSymbols, s_expectedHalfToFullFormsComparison }; + } + yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreCase, s_expectedHalfToFullFormsComparison }; // in HybridGlobalization on Browser IgnoreNonSpace is not supported and comparison of katakana/hiragana equivalents with supportedIgnoreNonSpaceOption gives 0 if (!PlatformDetection.IsHybridGlobalizationOnBrowser) @@ -248,9 +263,15 @@ namespace System.Globalization.Tests // in HybridGlobalization on Browser IgnoreKanaType is supported only for "ja" var kanaComparison = PlatformDetection.IsHybridGlobalizationOnBrowser ? s_japaneseCompare : s_invariantCompare; - 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, s_expectedHiraganaToKatakanaCompare }; + + // 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[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.IgnoreCase, PlatformDetection.IsHybridGlobalizationOnOSX ? 1 : s_expectedHiraganaToKatakanaCompare }; // Japanese [semi-]voiced sound mark yield return new object[] { s_invariantCompare, "\u306F", "\u3070", CompareOptions.IgnoreCase, -1 }; @@ -272,12 +293,16 @@ namespace System.Globalization.Tests // Misc differences between platforms bool useNls = PlatformDetection.IsNlsGlobalization; - var supportedCmpOptions = PlatformDetection.IsHybridGlobalizationOnBrowser ? - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase : - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; + if (!PlatformDetection.IsHybridGlobalizationOnOSX) + { + 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", supportedCmpOptions, useNls ? 1: 0 }; - yield return new object[] { s_invariantCompare, "'\u3000'", "''", supportedCmpOptions, 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 }; yield return new object[] { s_invariantCompare, "\u30FC", "\uFF70", CompareOptions.None, useNls ? 0 : -1 }; @@ -314,6 +339,7 @@ namespace System.Globalization.Tests } [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) { @@ -499,6 +525,7 @@ namespace System.Globalization.Tests } [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++) @@ -594,6 +621,7 @@ namespace System.Globalization.Tests } [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.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj b/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj index b9887c1..e476255 100644 --- a/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj +++ b/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj @@ -32,5 +32,7 @@ + + diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index a1c106a..90536bd 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4053,10 +4053,10 @@ AssemblyName.GetAssemblyName() is not supported on this platform. - CompareOptions = {0} are not supported when HybridGlobalization=true. Disable it to load larger ICU bundle, then use this option. + CompareOptions = {0} are not supported when HybridGlobalization=true on this platform. Disable it to load larger ICU bundle, then use this option. - CompareOptions = {0} are not supported for culture = {1} when HybridGlobalization=true. Disable it to load larger ICU bundle, then use this option. + CompareOptions = {0} are not supported for culture = {1} when HybridGlobalization=true on this platform. Disable it to load larger ICU bundle, then use this option. Match length calculation is not supported when HybridGlobalization=true. Disable it to load larger ICU bundle, then use this function. diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 418a391..2bd9120 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -330,6 +330,7 @@ + @@ -1273,6 +1274,9 @@ Common\Interop\Interop.Collation.cs + + Common\Interop\Interop.Collation.OSX.cs + Common\Interop\Interop.ICU.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.OSX.cs new file mode 100644 index 0000000..b965415 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.OSX.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace System.Globalization +{ + public partial class CompareInfo + { + private unsafe int CompareStringNative(ReadOnlySpan string1, ReadOnlySpan string2, CompareOptions options) + { + Debug.Assert(!GlobalizationMode.Invariant); + Debug.Assert(!GlobalizationMode.UseNls); + + AssertComparisonSupported(options); + + // GetReference may return nullptr if the input span is defaulted. The native layer handles + // this appropriately; no workaround is needed on the managed side. + int result; + fixed (char* pString1 = &MemoryMarshal.GetReference(string1)) + fixed (char* pString2 = &MemoryMarshal.GetReference(string2)) + { + result = Interop.Globalization.CompareStringNative(m_name, m_name.Length, pString1, string1.Length, pString2, string2.Length, options); + } + + Debug.Assert(result != -2); + + return result; + } + + private static void AssertComparisonSupported(CompareOptions options) + { + if ((options | SupportedCompareOptions) != SupportedCompareOptions) + throw new PlatformNotSupportedException(GetPNSE(options)); + } + + private const CompareOptions SupportedCompareOptions = CompareOptions.None | CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | + CompareOptions.IgnoreWidth | CompareOptions.StringSort; + + private static string GetPNSE(CompareOptions options) => + SR.Format(SR.PlatformNotSupported_HybridGlobalizationWithCompareOptions, 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 2c52633..53192ec 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs @@ -497,6 +497,9 @@ namespace System.Globalization #if TARGET_BROWSER GlobalizationMode.Hybrid ? JsCompareString(string1, string2, options) : +#elif TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + GlobalizationMode.Hybrid ? + CompareStringNative(string1, string2, options) : #endif IcuCompareString(string1, string2, options); diff --git a/src/mono/mono/mini/CMakeLists.txt b/src/mono/mono/mini/CMakeLists.txt index 49fe1b7..8e60bab 100644 --- a/src/mono/mono/mini/CMakeLists.txt +++ b/src/mono/mono/mini/CMakeLists.txt @@ -70,7 +70,8 @@ if(HAVE_SYS_ICU) if (TARGET_DARWIN) set(icu_shim_sources_base ${icu_shim_sources_base} - pal_locale.m) + pal_locale.m + pal_collation.m) endif() addprefix(icu_shim_sources "${ICU_SHIM_PATH}" "${icu_shim_sources_base}") diff --git a/src/native/libs/System.Globalization.Native/CMakeLists.txt b/src/native/libs/System.Globalization.Native/CMakeLists.txt index 37191a7..2cb63a4 100644 --- a/src/native/libs/System.Globalization.Native/CMakeLists.txt +++ b/src/native/libs/System.Globalization.Native/CMakeLists.txt @@ -89,7 +89,7 @@ else() endif() if (CLR_CMAKE_TARGET_APPLE) - set(NATIVEGLOBALIZATION_SOURCES ${NATIVEGLOBALIZATION_SOURCES} pal_locale.m) + set(NATIVEGLOBALIZATION_SOURCES ${NATIVEGLOBALIZATION_SOURCES} pal_locale.m pal_collation.m) endif() # time zone names are filtered out of icu data for the browser and associated functionality is disabled diff --git a/src/native/libs/System.Globalization.Native/entrypoints.c b/src/native/libs/System.Globalization.Native/entrypoints.c index c3c65b2..cc652d6 100644 --- a/src/native/libs/System.Globalization.Native/entrypoints.c +++ b/src/native/libs/System.Globalization.Native/entrypoints.c @@ -59,6 +59,7 @@ static const Entry s_globalizationNative[] = DllImportEntry(GlobalizationNative_ToUnicode) DllImportEntry(GlobalizationNative_WindowsIdToIanaId) #ifdef __APPLE__ + DllImportEntry(GlobalizationNative_CompareStringNative) DllImportEntry(GlobalizationNative_GetLocaleNameNative) DllImportEntry(GlobalizationNative_GetLocaleInfoStringNative) DllImportEntry(GlobalizationNative_GetLocaleInfoIntNative) diff --git a/src/native/libs/System.Globalization.Native/pal_collation.h b/src/native/libs/System.Globalization.Native/pal_collation.h index 76bc3bb..2aaff77 100644 --- a/src/native/libs/System.Globalization.Native/pal_collation.h +++ b/src/native/libs/System.Globalization.Native/pal_collation.h @@ -61,3 +61,13 @@ PALEXPORT int32_t GlobalizationNative_GetSortKey(SortHandle* pSortHandle, uint8_t* sortKey, int32_t cbSortKeyLength, int32_t options); + +#ifdef __APPLE__ +PALEXPORT int32_t GlobalizationNative_CompareStringNative(const uint16_t* localeName, + int32_t lNameLength, + const uint16_t* lpStr1, + int32_t cwStr1Length, + const uint16_t* lpStr2, + int32_t cwStr2Length, + 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 new file mode 100644 index 0000000..f0120e5 --- /dev/null +++ b/src/native/libs/System.Globalization.Native/pal_collation.m @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +#include "pal_locale_internal.h" +#include "pal_collation.h" + +#import + +#if defined(TARGET_OSX) || defined(TARGET_MACCATALYST) || defined(TARGET_IOS) || defined(TARGET_TVOS) + +// Enum that corresponds to C# CompareOptions +typedef enum +{ + None = 0, + IgnoreCase = 1, + IgnoreNonSpace = 2, + IgnoreWidth = 16, + StringSort = 536870912, +} CompareOptions; + +static NSStringCompareOptions ConvertFromCompareOptionsToNSStringCompareOptions(int32_t comparisonOptions) +{ + 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; + + if (comparisonOptions & IgnoreCase) + options |= NSCaseInsensitiveSearch; + + if (comparisonOptions & IgnoreNonSpace) + options |= NSDiacriticInsensitiveSearch; + + if (comparisonOptions & IgnoreWidth) + options |= NSWidthInsensitiveSearch; + + return options; +} + +/* +Function: +CompareString +*/ +int32_t GlobalizationNative_CompareStringNative(const uint16_t* localeName, int32_t lNameLength, const uint16_t* lpStr1, int32_t cwStr1Length, + const uint16_t* lpStr2, int32_t cwStr2Length, int32_t comparisonOptions) +{ + NSLocale *currentLocale; + if(localeName == NULL || lNameLength == 0) + { + currentLocale = [NSLocale systemLocale]; + } + else + { + NSString *locName = [NSString stringWithCharacters: localeName length: lNameLength]; + currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName]; + } + + NSString *firstString = [NSString stringWithCharacters: lpStr1 length: cwStr1Length]; + NSString *secondString = [NSString stringWithCharacters: lpStr2 length: cwStr2Length]; + NSRange string1Range = NSMakeRange(0, cwStr1Length); + NSStringCompareOptions options = ConvertFromCompareOptionsToNSStringCompareOptions(comparisonOptions); + + // in case mapping is not found + if (options == 0) + return -2; + + return [firstString compare:secondString + options:options + range:string1Range + locale:currentLocale]; +} + +#endif -- 2.7.4