[OSX] HybridGlobalization implement compare native function (#85965)
authorMeri Khamoyan <96171496+mkhamoyan@users.noreply.github.com>
Wed, 24 May 2023 11:08:56 +0000 (15:08 +0400)
committerGitHub <noreply@github.com>
Wed, 24 May 2023 11:08:56 +0000 (15:08 +0400)
Implemented CompareStringNative for OSX platforms
Added changes done by @ilonatommy in #86305

14 files changed:
docs/design/features/hybrid-globalization.md
src/libraries/Common/src/Interop/Interop.Collation.OSX.cs [new file with mode: 0644]
src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
src/libraries/System.Globalization/tests/CompareInfo/CompareInfoTests.Compare.cs
src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj
src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.OSX.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs
src/mono/mono/mini/CMakeLists.txt
src/native/libs/System.Globalization.Native/CMakeLists.txt
src/native/libs/System.Globalization.Native/entrypoints.c
src/native/libs/System.Globalization.Native/pal_collation.h
src/native/libs/System.Globalization.Native/pal_collation.m [new file with mode: 0644]

index d9f7e30..916bada 100644 (file)
@@ -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 (file)
index 0000000..d9e5022
--- /dev/null
@@ -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);
+    }
+}
index e26bbb9..68ca799 100644 (file)
@@ -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);
index c7c24b9..eb4d7e9 100644 (file)
@@ -41,78 +41,79 @@ namespace System.Globalization.Tests
 
         public static IEnumerable<object[]> 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)
         {
index b9887c1..e476255 100644 (file)
@@ -32,5 +32,7 @@
     <Compile Include="..\NumberFormatInfo\NumberFormatInfoPercentNegativePattern.cs" />
     <Compile Include="..\NumberFormatInfo\NumberFormatInfoPercentGroupSizes.cs" />
     <Compile Include="..\NumberFormatInfo\NumberFormatInfoPercentPositivePattern.cs" />
+    <Compile Include="..\CompareInfo\CompareInfoTestsBase.cs" />
+    <Compile Include="..\CompareInfo\CompareInfoTests.Compare.cs" />
   </ItemGroup>
 </Project>
index a1c106a..90536bd 100644 (file)
     <value>AssemblyName.GetAssemblyName() is not supported on this platform.</value>
   </data>
   <data name="PlatformNotSupported_HybridGlobalizationWithCompareOptions" xml:space="preserve">
-    <value>CompareOptions = {0} are not supported when HybridGlobalization=true. Disable it to load larger ICU bundle, then use this option.</value>
+    <value>CompareOptions = {0} are not supported when HybridGlobalization=true on this platform. Disable it to load larger ICU bundle, then use this option.</value>
   </data>
   <data name="PlatformNotSupported_HybridGlobalizationWithCompareOptionsForCulture" xml:space="preserve">
-    <value>CompareOptions = {0} are not supported for culture = {1} when HybridGlobalization=true. Disable it to load larger ICU bundle, then use this option.</value>
+    <value>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.</value>
   </data>
   <data name="PlatformNotSupported_HybridGlobalizationWithMatchLength" xml:space="preserve">
     <value>Match length calculation is not supported when HybridGlobalization=true. Disable it to load larger ICU bundle, then use this function.</value>
index 418a391..2bd9120 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CompareInfo.Invariant.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CompareInfo.Nls.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CompareInfo.WebAssembly.cs" Condition="'$(TargetsBrowser)' == 'true'" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CompareInfo.OSX.cs" Condition="'$(IsOSXLike)' == 'true'" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CompareOptions.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.Icu.cs" />
     <Compile Include="$(CommonPath)Interop\Interop.Collation.cs">
       <Link>Common\Interop\Interop.Collation.cs</Link>
     </Compile>
+     <Compile Include="$(CommonPath)Interop\Interop.Collation.OSX.cs" Condition="'$(IsOSXLike)' == 'true'">
+      <Link>Common\Interop\Interop.Collation.OSX.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)Interop\Interop.ICU.cs">
       <Link>Common\Interop\Interop.ICU.cs</Link>
     </Compile>
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 (file)
index 0000000..b965415
--- /dev/null
@@ -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<char> string1, ReadOnlySpan<char> 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);
+    }
+}
index 2c52633..53192ec 100644 (file)
@@ -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);
 
index 49fe1b7..8e60bab 100644 (file)
@@ -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}")
index 37191a7..2cb63a4 100644 (file)
@@ -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
index c3c65b2..cc652d6 100644 (file)
@@ -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)
index 76bc3bb..2aaff77 100644 (file)
@@ -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 (file)
index 0000000..f0120e5
--- /dev/null
@@ -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 <stdlib.h>
+#include "pal_locale_internal.h"
+#include "pal_collation.h"
+
+#import <Foundation/Foundation.h>
+
+#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