diff --git a/docs/design/features/hybrid-globalization.md b/docs/design/features/hybrid-globalization.md index 38d89a448792b..9583aa067aea4 100644 --- a/docs/design/features/hybrid-globalization.md +++ b/docs/design/features/hybrid-globalization.md @@ -18,3 +18,157 @@ Affected public APIs: - TextInfo.ToTitleCase. Case change with invariant culture uses `toUpperCase` / `toLoweCase` functions that do not guarantee a full match with the original invariant culture. + +**String comparison** + +Affected public APIs: +- CompareInfo.Compare, +- String.Compare, +- String.Equals. + +The number of `CompareOptions` and `StringComparison` combinations is limited. Originally supported combinations can be found [here for CompareOptions](https://learn.microsoft.com/dotnet/api/system.globalization.compareoptions) and [here for StringComparison](https://learn.microsoft.com/dotnet/api/system.stringcomparison). + +- `IgnoreWidth` is not supported because there is no equivalent in Web API. Throws `PlatformNotSupportedException`. +``` JS +let high = String.fromCharCode(65281) // %uff83 = テ +let low = String.fromCharCode(12486) // %u30c6 = テ +high.localeCompare(low, "ja-JP", { sensitivity: "case" }) // -1 ; case: a ≠ b, a = á, a ≠ A; expected: 0 + +let wide = String.fromCharCode(65345) // %uFF41 = a +let narrow = "a" +wide.localeCompare(narrow, "en-US", { sensitivity: "accent" }) // 0; accent: a ≠ b, a ≠ á, a = A; expected: -1 +``` + +For comparison where "accent" sensitivity is used, ignoring some type of character widths is applied and cannot be switched off (see: point about `IgnoreCase`). + +- `IgnoreKanaType`: + +It is always switched on for comparison with locale "ja-JP", even if this comparison option was not set explicitly. + +``` JS +let hiragana = String.fromCharCode(12353) // %u3041 = ぁ +let katakana = String.fromCharCode(12449) // %u30A1 = ァ +let enCmp = hiragana.localeCompare(katakana, "en-US") // -1 +let jaCmp = hiragana.localeCompare(katakana, "ja-JP") // 0 +``` + +For locales different than "ja-JP" it cannot be used separately (no equivalent in Web API) - throws `PlatformNotSupportedException`. + +- `None`: + +No equivalent in Web API for "ja-JP" locale. See previous point about `IgnoreKanaType`. For "ja-JP" it throws `PlatformNotSupportedException`. + +- `IgnoreCase`, `CurrentCultureIgnoreCase`, `InvariantCultureIgnoreCase` + +For `IgnoreCase | IgnoreKanaType`, argument `sensitivity: "accent"` is used. + +``` JS +let hiraganaBig = `${String.fromCharCode(12353)} A` // %u3041 = ぁ +let katakanaSmall = `${String.fromCharCode(12449)} a` // %u30A1 = ァ +hiraganaBig.localeCompare(katakanaSmall, "en-US", { sensitivity: "accent" }) // 0; accent: a ≠ b, a ≠ á, a = A +``` + +Known exceptions: + +| **character 1** | **character 2** | **CompareOptions** | **hybrid globalization** | **icu** | **comments** | +|:---------------:|:---------------:|--------------------|:------------------------:|:-------:|:-------------------------------------------------------:| +| a | `\uFF41` a | IgnoreKanaType | 0 | -1 | applies to all wide-narrow chars | +| `\u30DC` ボ | `\uFF8E` ホ | IgnoreCase | 1 | -1 | 1 is returned in icu when we additionally ignore width | +| `\u30BF` タ | `\uFF80` タ | IgnoreCase | 0 | -1 | | + + +For `IgnoreCase` alone, a comparison with default option: `sensitivity: "variant"` is used after string case unification. + +``` JS +let hiraganaBig = `${String.fromCharCode(12353)} A` // %u3041 = ぁ +let katakanaSmall = `${String.fromCharCode(12449)} a` // %u30A1 = ァ +let unchangedLocale = "en-US" +let unchangedStr1 = hiraganaBig.toLocaleLowerCase(unchangedLocale); +let unchangedStr2 = katakanaSmall.toLocaleLowerCase(unchangedLocale); +unchangedStr1.localeCompare(unchangedStr2, unchangedLocale) // -1; +let changedLocale = "ja-JP" +let changedStr1 = hiraganaBig.toLocaleLowerCase(changedLocale); +let changedStr2 = katakanaSmall.toLocaleLowerCase(changedLocale); +changedStr1.localeCompare(changedStr2, changedLocale) // 0; +``` + +From this reason, comparison with locale `ja-JP` `CompareOption` `IgnoreCase` and `StringComparison`: `CurrentCultureIgnoreCase` and `InvariantCultureIgnoreCase` behave like a combination `IgnoreCase | IgnoreKanaType` (see: previous point about `IgnoreKanaType`). For other locales the behavior is unchanged with the following known exceptions: + +| **character 1** | **character 2** | **CompareOptions** | **hybrid globalization** | **icu** | +|:------------------------------------------------:|:----------------------------------------------------------:|-----------------------------------|:------------------------:|:-------:| +| `\uFF9E` (HALFWIDTH KATAKANA VOICED SOUND MARK) | `\u3099` (COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK) | None / IgnoreCase / IgnoreSymbols | 1 | 0 | + +- `IgnoreNonSpace` + +`IgnoreNonSpace` cannot be used separately without `IgnoreKanaType`. Argument `sensitivity: "case"` is used for comparison and it ignores both types of characters. Option `IgnoreNonSpace` alone throws `PlatformNotSupportedException`. + +``` JS +let hiraganaAccent = `${String.fromCharCode(12353)} á` // %u3041 = ぁ +let katakanaNoAccent = `${String.fromCharCode(12449)} a` // %u30A1 = ァ +hiraganaAccent.localeCompare(katakanaNoAccent, "en-US", { sensitivity: "case" }) // 0; case: a ≠ b, a = á, a ≠ A +``` + +- `IgnoreNonSpace | IgnoreCase` +Combination of `IgnoreNonSpace` and `IgnoreCase` cannot be used without `IgnoreKanaType`. Argument `sensitivity: "base"` is used for comparison and it ignores three types of characters. Combination `IgnoreNonSpace | IgnoreCase` alone throws `PlatformNotSupportedException`. + +``` JS +let hiraganaBigAccent = `${String.fromCharCode(12353)} A á` // %u3041 = ぁ +let katakanaSmallNoAccent = `${String.fromCharCode(12449)} a a` // %u30A1 = ァ +hiraganaBigAccent.localeCompare(katakanaSmallNoAccent, "en-US", { sensitivity: "base" }) // 0; base: a ≠ b, a = á, a = A +``` + +- `IgnoreSymbols` + +The subset of ignored symbols is limited to the symbols ignored by `string1.localeCompare(string2, locale, { ignorePunctuation: true })`. E.g. currency symbols, & are not ignored + +``` JS +let hiraganaAccent = `${String.fromCharCode(12353)} á` // %u3041 = ぁ +let katakanaNoAccent = `${String.fromCharCode(12449)} a` // %u30A1 = ァ +hiraganaBig.localeCompare(katakanaSmall, "en-US", { sensitivity: "base" }) // 0; base: a ≠ b, a = á, a = A +``` + +- List of all `CompareOptions` combinations always throwing `PlatformNotSupportedException`: + +`IgnoreCase`, + +`IgnoreNonSpace`, + +`IgnoreNonSpace | IgnoreCase`, + +`IgnoreSymbols | IgnoreCase`, + +`IgnoreSymbols | IgnoreNonSpace`, + +`IgnoreSymbols | IgnoreNonSpace | IgnoreCase`, + +`IgnoreWidth`, + +`IgnoreWidth | IgnoreCase`, + +`IgnoreWidth | IgnoreNonSpace`, + +`IgnoreWidth | IgnoreNonSpace | IgnoreCase`, + +`IgnoreWidth | IgnoreSymbols` + +`IgnoreWidth | IgnoreSymbols | IgnoreCase` + +`IgnoreWidth | IgnoreSymbols | IgnoreNonSpace` + +`IgnoreWidth | IgnoreSymbols | IgnoreNonSpace | IgnoreCase` + +`IgnoreKanaType | IgnoreWidth` + +`IgnoreKanaType | IgnoreWidth | IgnoreCase` + +`IgnoreKanaType | IgnoreWidth | IgnoreNonSpace` + +`IgnoreKanaType | IgnoreWidth | IgnoreNonSpace | IgnoreCase` + +`IgnoreKanaType | IgnoreWidth | IgnoreSymbols` + +`IgnoreKanaType | IgnoreWidth | IgnoreSymbols | IgnoreCase` + +`IgnoreKanaType | IgnoreWidth | IgnoreSymbols | IgnoreNonSpace` + +`IgnoreKanaType | IgnoreWidth | IgnoreSymbols | IgnoreNonSpace | IgnoreCase` diff --git a/src/libraries/Common/src/Interop/Browser/Interop.CompareInfo.cs b/src/libraries/Common/src/Interop/Browser/Interop.CompareInfo.cs new file mode 100644 index 0000000000000..693b908a0c691 --- /dev/null +++ b/src/libraries/Common/src/Interop/Browser/Interop.CompareInfo.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +internal static partial class Interop +{ + internal static unsafe partial class JsGlobalization + { + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern unsafe int CompareString(out string exceptionMessage, in string culture, char* str1, int str1Len, char* str2, int str2Len, global::System.Globalization.CompareOptions options); + } +} diff --git a/src/libraries/Common/src/Interop/Browser/Interop.TextInfo.cs b/src/libraries/Common/src/Interop/Browser/Interop.TextInfo.cs new file mode 100644 index 0000000000000..8ab038b851e63 --- /dev/null +++ b/src/libraries/Common/src/Interop/Browser/Interop.TextInfo.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +internal static partial class Interop +{ + internal static unsafe partial class JsGlobalization + { + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern unsafe void ChangeCaseInvariant(out string exceptionMessage, char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern unsafe void ChangeCase(out string exceptionMessage, in string culture, char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper); + } +} diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 6e43ad25bc6b5..c72b550b4abfe 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -348,10 +348,14 @@ public static string GetDistroVersionString() private static readonly Lazy m_isInvariant = new Lazy(() => (bool?)Type.GetType("System.Globalization.GlobalizationMode")?.GetProperty("Invariant", BindingFlags.NonPublic | BindingFlags.Static)?.GetValue(null) == true); + private static readonly Lazy m_isHybrid = new Lazy(() + => (bool?)Type.GetType("System.Globalization.GlobalizationMode")?.GetProperty("Hybrid", BindingFlags.NonPublic | BindingFlags.Static)?.GetValue(null) == true); + private static readonly Lazy m_icuVersion = new Lazy(GetICUVersion); public static Version ICUVersion => m_icuVersion.Value; public static bool IsInvariantGlobalization => m_isInvariant.Value; + public static bool IsHybridGlobalizationOnWasm => m_isHybrid.Value && (IsBrowser || IsWasi); public static bool IsNotInvariantGlobalization => !IsInvariantGlobalization; public static bool IsIcuGlobalization => ICUVersion > new Version(0, 0, 0, 0); public static bool IsNlsGlobalization => IsNotInvariantGlobalization && !IsIcuGlobalization; diff --git a/src/libraries/System.Globalization/tests/CompareInfo/CompareInfoTests.Compare.cs b/src/libraries/System.Globalization/tests/CompareInfo/CompareInfoTests.Compare.cs index 94187f7c5f776..c0f7851b07fb6 100644 --- a/src/libraries/System.Globalization/tests/CompareInfo/CompareInfoTests.Compare.cs +++ b/src/libraries/System.Globalization/tests/CompareInfo/CompareInfoTests.Compare.cs @@ -13,6 +13,16 @@ public class CompareInfoCompareTests private static CompareInfo s_currentCompare = CultureInfo.CurrentCulture.CompareInfo; private static CompareInfo s_hungarianCompare = new CultureInfo("hu-HU").CompareInfo; private static CompareInfo s_turkishCompare = new CultureInfo("tr-TR").CompareInfo; + private static CompareInfo s_japaneseCompare = new CultureInfo("ja-JP").CompareInfo; + private static CompareOptions supportedIgnoreNonSpaceOption = + PlatformDetection.IsHybridGlobalizationOnWasm ? + CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreKanaType : + CompareOptions.IgnoreNonSpace; + + private static CompareOptions supportedIgnoreCaseIgnoreNonSpaceOptions = + PlatformDetection.IsHybridGlobalizationOnWasm ? + CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreKanaType : + CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace; // On Windows, hiragana characters sort after katakana. // On ICU, it is the opposite @@ -27,15 +37,20 @@ public class CompareInfoCompareTests public static IEnumerable Compare_Kana_TestData() { - CompareOptions ignoreKanaIgnoreWidthIgnoreCase = CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u3060", "\uFF80\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30C7\u30BF\u30D9\u30B9", "\uFF83\uFF9E\uFF80\uFF8D\uFF9E\uFF7D", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30C7", "\uFF83\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30C7\u30BF", "\uFF83\uFF9E\uFF80", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30C7\u30BF\u30D9", "\uFF83\uFF9E\uFF80\uFF8D\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\uFF83\uFF9E\uFF70\uFF80\uFF8D\uFF9E\uFF70\uFF7D", "\u3067\u30FC\u305F\u3079\u30FC\u3059", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + // HybridGlobalization does not support IgnoreWidth + if (!PlatformDetection.IsHybridGlobalizationOnWasm) + { + CompareOptions ignoreKanaIgnoreWidthIgnoreCase = CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; + yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u3060", "\uFF80\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30C7\u30BF\u30D9\u30B9", "\uFF83\uFF9E\uFF80\uFF8D\uFF9E\uFF7D", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30C7", "\uFF83\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30C7\u30BF", "\uFF83\uFF9E\uFF80", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30C7\u30BF\u30D9", "\uFF83\uFF9E\uFF80\uFF8D\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u3067\u30FC\u305F\u3079\u30FC\u3059", "\uFF83\uFF9E\uFF70\uFF80\uFF8D\uFF9E\uFF70\uFF7D", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + } + yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; yield return new object[] { s_invariantCompare, "\u3060", "\uFF80\uFF9E", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; @@ -50,68 +65,72 @@ public static IEnumerable Compare_Kana_TestData() public static IEnumerable Compare_TestData() { - CompareOptions ignoreKanaIgnoreWidthIgnoreCase = CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; - yield return new object[] { s_invariantCompare, "\u3042", "\u30A2", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u3042", "\uFF71", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u30E3", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u3083", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u304D \u3083", "\u30AD\u3083", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3044", "I", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - - yield return new object[] { s_invariantCompare, "a", "A", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "a", "\uFF41", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23\uFF24\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23D\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "a\uFF22\uFF23D\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF41\uFF42\uFF23D\uFF25", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - - yield return new object[] { s_invariantCompare, "\u6FA4", "\u6CA2", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u30D6\u30D9\u30DC", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\u30DC", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u3079\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D6", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3071\u3074\u30D7\u307A", "\uFF8B\uFF9F\uFF8C\uFF9F", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u3070\uFF8E\uFF9E\u30D6", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C\u3079\u307C", "\u3079\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3070\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\u30D6", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - - yield return new object[] { s_invariantCompare, "ABDDE", "D", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF43D", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "ABCDE", "c", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u3060", "\u305F", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - - yield return new object[] { s_invariantCompare, "\u30BF", "\uFF80", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - - yield return new object[] { s_invariantCompare, "\u68EE\u9D0E\u5916", "\u68EE\u9DD7\u5916", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u68EE\u9DD7\u5916", "\u68EE\u9DD7\u5916", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u2019\u2019\u2019\u2019", "''''", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u2019", "'", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "", "'", ignoreKanaIgnoreWidthIgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u4E00", "\uFF11", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u2160", "\uFF11", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - - yield return new object[] { s_invariantCompare, "0", "\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "10", "1\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "9999\uFF1910", "1\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "9999\uFF191010", "1\uFF10", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - - yield return new object[] { s_invariantCompare, "'\u3000'", "' '", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\uFF1B", ";", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\uFF08", "(", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\uFF70", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\uFF0D", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\u30FC", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\u2015", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - yield return new object[] { s_invariantCompare, "\u30FC", "\u2010", ignoreKanaIgnoreWidthIgnoreCase, 1 }; - - yield return new object[] { s_invariantCompare, "/", "\uFF0F", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "'", "\uFF07", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\"", "\uFF02", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + // PlatformDetection.IsHybridGlobalizationOnWasm does not support IgnoreWidth + CompareOptions ignoredOptions = PlatformDetection.IsHybridGlobalizationOnWasm ? + 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 }; yield return new object[] { s_invariantCompare, "\u3042", "\u30A2", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; @@ -203,7 +222,7 @@ public static IEnumerable Compare_TestData() 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", CompareOptions.IgnoreNonSpace, 0 }; + yield return new object[] { s_invariantCompare, "FooBA\u0300R", "FooB\u00C0R", supportedIgnoreNonSpaceOption, 0 }; yield return new object[] { s_invariantCompare, "Test's", "Tests", CompareOptions.IgnoreSymbols, 0 }; yield return new object[] { s_invariantCompare, "Test's", "Tests", CompareOptions.StringSort, -1 }; @@ -220,30 +239,42 @@ public static IEnumerable Compare_TestData() yield return new object[] { s_invariantCompare, "", "", CompareOptions.None, 0 }; yield return new object[] { s_invariantCompare, new string('a', 5555), new string('a', 5555), CompareOptions.None, 0 }; - yield return new object[] { s_invariantCompare, "foobar", "FooB\u00C0R", CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "foobar", "FooB\u00C0R", CompareOptions.IgnoreNonSpace, -1 }; + yield return new object[] { s_invariantCompare, "foobar", "FooB\u00C0R", supportedIgnoreCaseIgnoreNonSpaceOptions, 0 }; + yield return new object[] { s_invariantCompare, "foobar", "FooB\u00C0R", supportedIgnoreNonSpaceOption, -1 }; - yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", CompareOptions.IgnoreNonSpace, 0 }; - yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", CompareOptions.IgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u20A9", "\uFFE6", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", supportedIgnoreNonSpaceOption, 0 }; + yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", CompareOptions.IgnoreCase, PlatformDetection.IsHybridGlobalizationOnWasm ? 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 }; - yield return new object[] { s_invariantCompare, "\u00A2", "\uFFE0", CompareOptions.IgnoreSymbols, 0 }; - yield return new object[] { s_invariantCompare, "$", "&", CompareOptions.IgnoreSymbols, 0 }; + // some symbols e.g. currencies are not ignored correctly in HybridGlobalization + if (!PlatformDetection.IsHybridGlobalizationOnWasm) + { + yield return new object[] { s_invariantCompare, "\u00A2", "\uFFE0", CompareOptions.IgnoreSymbols, 0 }; + yield return new object[] { s_invariantCompare, "$", "&", CompareOptions.IgnoreSymbols, 0 }; + } yield return new object[] { s_invariantCompare, "\uFF65", "\u30FB", CompareOptions.IgnoreSymbols, 0 }; - yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.IgnoreWidth, 0 }; yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.None, -1 }; - yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreWidth, 0 }; + + if (!PlatformDetection.IsHybridGlobalizationOnWasm) + { + yield return new object[] { s_invariantCompare, "\u20A9", "\uFFE6", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreWidth, 0 }; + } yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreSymbols, s_expectedHalfToFullFormsComparison }; yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreCase, s_expectedHalfToFullFormsComparison }; - yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreNonSpace, s_expectedHalfToFullFormsComparison }; + // in HybridGlobalization on WASM IgnoreNonSpace is not supported and comparison of katakana/hiragana equivalents with supportedIgnoreNonSpaceOption gives 0 + if (!PlatformDetection.IsHybridGlobalizationOnWasm) + yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreNonSpace, s_expectedHalfToFullFormsComparison }; yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.None, s_expectedHalfToFullFormsComparison }; - yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.IgnoreKanaType, 0 }; + // in HybridGlobalization on WASM IgnoreKanaType is supported only for "ja" + var kanaComparison = PlatformDetection.IsHybridGlobalizationOnWasm ? 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 }; - yield return new object[] { s_invariantCompare, "c", "C", CompareOptions.IgnoreKanaType, -1 }; // Japanese [semi-]voiced sound mark yield return new object[] { s_invariantCompare, "\u306F", "\u3070", CompareOptions.IgnoreCase, -1 }; @@ -252,12 +283,12 @@ public static IEnumerable Compare_TestData() yield return new object[] { s_invariantCompare, "\u30CF", "\u30D0", CompareOptions.IgnoreCase, -1 }; yield return new object[] { s_invariantCompare, "\u30CF", "\u30D1", CompareOptions.IgnoreCase, -1 }; yield return new object[] { s_invariantCompare, "\u30D0", "\u30D1", CompareOptions.IgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u306F", "\u3070", CompareOptions.IgnoreNonSpace, 0 }; - yield return new object[] { s_invariantCompare, "\u306F", "\u3071", CompareOptions.IgnoreNonSpace, 0 }; - yield return new object[] { s_invariantCompare, "\u3070", "\u3071", CompareOptions.IgnoreNonSpace, 0 }; - yield return new object[] { s_invariantCompare, "\u30CF", "\u30D0", CompareOptions.IgnoreNonSpace, 0 }; - yield return new object[] { s_invariantCompare, "\u30CF", "\u30D1", CompareOptions.IgnoreNonSpace, 0 }; - yield return new object[] { s_invariantCompare, "\u30D0", "\u30D1", CompareOptions.IgnoreNonSpace, 0 }; + yield return new object[] { s_invariantCompare, "\u306F", "\u3070", supportedIgnoreNonSpaceOption, 0 }; + yield return new object[] { s_invariantCompare, "\u306F", "\u3071", supportedIgnoreNonSpaceOption, 0 }; + yield return new object[] { s_invariantCompare, "\u3070", "\u3071", supportedIgnoreNonSpaceOption, 0 }; + yield return new object[] { s_invariantCompare, "\u30CF", "\u30D0", supportedIgnoreNonSpaceOption, 0 }; + yield return new object[] { s_invariantCompare, "\u30CF", "\u30D1", supportedIgnoreNonSpaceOption, 0 }; + yield return new object[] { s_invariantCompare, "\u30D0", "\u30D1", supportedIgnoreNonSpaceOption, 0 }; // Spanish yield return new object[] { new CultureInfo("es-ES").CompareInfo, "llegar", "lugar", CompareOptions.None, -1 }; @@ -265,8 +296,12 @@ public static IEnumerable Compare_TestData() // Misc differences between platforms bool useNls = PlatformDetection.IsNlsGlobalization; - yield return new object[] { s_invariantCompare, "\u3042", "\u30A1", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, useNls ? 1: 0 }; - yield return new object[] { s_invariantCompare, "'\u3000'", "''", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, useNls ? 1 : -1 }; + var supportedCmpOptions = PlatformDetection.IsHybridGlobalizationOnWasm ? + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase : + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; + + 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 }; @@ -304,7 +339,7 @@ public void CompareWithUnassignedChars() { int result = PlatformDetection.IsNlsGlobalization ? 0 : -1; Compare(s_invariantCompare, "FooBar", "Foo\uFFFFBar", CompareOptions.None, result); - Compare(s_invariantCompare, "FooBar", "Foo\uFFFFBar", CompareOptions.IgnoreNonSpace, result); + Compare(s_invariantCompare, "FooBar", "Foo\uFFFFBar", supportedIgnoreNonSpaceOption, result); } [ConditionalTheory(nameof(IsNotWindowsKanaRegressedVersion))] @@ -503,27 +538,97 @@ 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'"); - Assert.True(string.Compare("\u3162\u3163", "\uFFDB\uFFDC", CultureInfo.InvariantCulture, CompareOptions.IgnoreWidth) == 0, "Expect '0x3162 0x3163' == '0xFFDB 0xFFDC'"); + if (!PlatformDetection.IsHybridGlobalizationOnWasm) + Assert.True(string.Compare("\u3162\u3163", "\uFFDB\uFFDC", CultureInfo.InvariantCulture, CompareOptions.IgnoreWidth) == 0, "Expect '0x3162 0x3163' == '0xFFDB 0xFFDC'"); const char hiraganaStart = '\u3041'; const char hiraganaEnd = '\u3096'; const int hiraganaToKatakanaOffset = 0x30a1 - 0x3041; + // in HybridGlobalization on WASM IgnoreKanaType is supported only for "ja-JP" + CultureInfo ignoreKanaTypeTestedCulture = PlatformDetection.IsHybridGlobalizationOnWasm ? new CultureInfo("ja-JP") : CultureInfo.InvariantCulture; + for (Char hiraganaChar = hiraganaStart; hiraganaChar <= hiraganaEnd; hiraganaChar++) { Assert.False(string.Compare(new string(hiraganaChar, 1), new string((char)(hiraganaChar + hiraganaToKatakanaOffset), 1), CultureInfo.InvariantCulture, CompareOptions.IgnoreCase) == 0, $"Expect '{(int)hiraganaChar:x4}' != {(int)hiraganaChar + hiraganaToKatakanaOffset:x4} with CompareOptions.IgnoreCase"); - Assert.True(string.Compare(new string(hiraganaChar, 1), new string((char)(hiraganaChar + hiraganaToKatakanaOffset), 1), CultureInfo.InvariantCulture, CompareOptions.IgnoreKanaType) == 0, + Assert.True(string.Compare(new string(hiraganaChar, 1), new string((char)(hiraganaChar + hiraganaToKatakanaOffset), 1), ignoreKanaTypeTestedCulture, CompareOptions.IgnoreKanaType) == 0, $"Expect '{(int)hiraganaChar:x4}' == {(int)hiraganaChar + hiraganaToKatakanaOffset:x4} with CompareOptions.IgnoreKanaType"); } } - [Fact] - public void TestHiraganaAndKatakana() + public static IEnumerable Compare_HiraganaAndKatakana_TestData() + { + CompareOptions[] optionsPositive = PlatformDetection.IsHybridGlobalizationOnWasm ? + new[] { + CompareOptions.None, + CompareOptions.IgnoreCase, + CompareOptions.IgnoreSymbols, + CompareOptions.IgnoreSymbols | CompareOptions.IgnoreCase, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreSymbols, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, + CompareOptions.Ordinal, + CompareOptions.OrdinalIgnoreCase, + } : + new[] { + CompareOptions.None, + CompareOptions.IgnoreCase, + CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreSymbols, + 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, + }; + CompareOptions[] optionsNegative = PlatformDetection.IsHybridGlobalizationOnWasm ? + new[] { + CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, + CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, + CompareOptions.IgnoreWidth, + CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, + CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, + CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols, + CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreCase, + CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, + CompareOptions.IgnoreKanaType, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreCase, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace, + CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, + } : + Array.Empty(); + yield return new object[] { optionsPositive, optionsNegative }; + } + + [Theory] + [MemberData(nameof(Compare_HiraganaAndKatakana_TestData))] + public void TestHiraganaAndKatakana(CompareOptions[] optionsPositive, CompareOptions[] optionsNegative) { const char hiraganaStart = '\u3041'; const char hiraganaEnd = '\u3096'; - const int hiraganaToKatakanaOffset = 0x30a1 - 0x3041; + const int hiraganaToKatakanaOffset = 0x30a1 - 0x3041; //12449 - 12353 = 96 List hiraganaList = new List(); for (char c = hiraganaStart; c <= hiraganaEnd; c++) // Hiragana { @@ -553,27 +658,7 @@ public void TestHiraganaAndKatakana() { hiraganaList.Add(c); } - CompareOptions[] options = new[] { - CompareOptions.None, - CompareOptions.IgnoreCase, - CompareOptions.IgnoreNonSpace, - CompareOptions.IgnoreSymbols, - 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, - }; - - foreach (var option in options) + foreach (var option in optionsPositive) { for (int i = 0; i < hiraganaList.Count; i++) { @@ -592,6 +677,13 @@ public void TestHiraganaAndKatakana() } } } + foreach (var option in optionsNegative) + { + char hiraganaChar1 = hiraganaList[0]; + char katakanaChar1 = (char)(hiraganaChar1 + hiraganaToKatakanaOffset); + Assert.Throws( + () => s_invariantCompare.Compare(new string(hiraganaChar1, 1), new string(katakanaChar1, 1), option)); + } } } } diff --git a/src/libraries/System.Globalization/tests/Hybrid/Hybrid.WASM.Tests.csproj b/src/libraries/System.Globalization/tests/Hybrid/Hybrid.WASM.Tests.csproj index b3a7e29367b02..bc7f90d1a82e6 100644 --- a/src/libraries/System.Globalization/tests/Hybrid/Hybrid.WASM.Tests.csproj +++ b/src/libraries/System.Globalization/tests/Hybrid/Hybrid.WASM.Tests.csproj @@ -6,5 +6,6 @@ + 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 d03e7042d0d8e..17e510838bfa4 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 @@ -343,6 +343,7 @@ + @@ -1259,6 +1260,12 @@ + + Common\Interop\Interop.CompareInfo.cs + + + Common\Interop\Interop.TextInfo.cs + Common\Interop\Interop.Calendar.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.WebAssembly.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.WebAssembly.cs new file mode 100644 index 0000000000000..3ca001a9573bd --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.WebAssembly.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace System.Globalization +{ + public partial class CompareInfo + { + private unsafe int JsCompareString(ReadOnlySpan string1, ReadOnlySpan string2, CompareOptions options) + { + Debug.Assert(!GlobalizationMode.Invariant); + Debug.Assert(!GlobalizationMode.UseNls); + Debug.Assert(GlobalizationMode.Hybrid); + Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + + + if (CompareOptionsNotSupported(options)) + throw new PlatformNotSupportedException(GetPNSE(options)); + + string cultureName = m_name; + + if (CompareOptionsNotSupportedForCulture(options, cultureName)) + throw new PlatformNotSupportedException(GetPNSEForCulture(options, cultureName)); + + string exceptionMessage; + int cmpResult; + fixed (char* pString1 = &MemoryMarshal.GetReference(string1)) + fixed (char* pString2 = &MemoryMarshal.GetReference(string2)) + { + cmpResult = Interop.JsGlobalization.CompareString(out exceptionMessage, cultureName, pString1, string1.Length, pString2, string2.Length, options); + } + + if (!string.IsNullOrEmpty(exceptionMessage)) + throw new Exception(exceptionMessage); + + return cmpResult; + } + + private static bool CompareOptionsNotSupported(CompareOptions options) => + (options & CompareOptions.IgnoreWidth) == CompareOptions.IgnoreWidth || + ((options & CompareOptions.IgnoreNonSpace) == CompareOptions.IgnoreNonSpace && (options & CompareOptions.IgnoreKanaType) != CompareOptions.IgnoreKanaType); + + + private static string GetPNSE(CompareOptions options) => + $"CompareOptions = {options} are not supported when HybridGlobalization=true. Disable it to load larger ICU bundle, then use this option."; + + + private static bool CompareOptionsNotSupportedForCulture(CompareOptions options, string cultureName) => + (options == CompareOptions.IgnoreKanaType && + (string.IsNullOrEmpty(cultureName) || cultureName.Split('-')[0] != "ja")) || + (options == CompareOptions.None && + (cultureName.Split('-')[0] == "ja")); + + + private static string GetPNSEForCulture(CompareOptions options, string cultureName) => + $"CompareOptions = {options} are not supported for culture = {cultureName} when HybridGlobalization=true. Disable it to load larger ICU bundle, then use this option."; + } +} 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 f0e89f12deff9..b7c47a684f349 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs @@ -487,6 +487,10 @@ private static void ThrowCompareOptionsCheckFailed(CompareOptions options) private unsafe int CompareStringCore(ReadOnlySpan string1, ReadOnlySpan string2, CompareOptions options) => GlobalizationMode.UseNls ? NlsCompareString(string1, string2, options) : +#if TARGET_BROWSER || TARGET_WASI + GlobalizationMode.Hybrid ? + JsCompareString(string1, string2, options) : +#endif IcuCompareString(string1, string2, options); /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.WebAssembly.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.WebAssembly.cs index 64aed60f3437b..e5ad1ec6420a4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.WebAssembly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.WebAssembly.cs @@ -1,20 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Diagnostics; -using System.Runtime.CompilerServices; namespace System.Globalization { - internal static unsafe class TextInfoInterop - { - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern unsafe void ChangeCaseInvariantJS(out string exceptionMessage, char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper); - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern unsafe void ChangeCaseJS(out string exceptionMessage, in string culture, char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper); - } - public partial class TextInfo { internal unsafe void JsChangeCase(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool toUpper) @@ -26,15 +16,14 @@ internal unsafe void JsChangeCase(char* src, int srcLen, char* dstBuffer, int ds string exceptionMessage; if (HasEmptyCultureName) { - TextInfoInterop.ChangeCaseInvariantJS(out exceptionMessage, src, srcLen, dstBuffer, dstBufferCapacity, toUpper); + Interop.JsGlobalization.ChangeCaseInvariant(out exceptionMessage, src, srcLen, dstBuffer, dstBufferCapacity, toUpper); } else { - TextInfoInterop.ChangeCaseJS(out exceptionMessage, _cultureName, src, srcLen, dstBuffer, dstBufferCapacity, toUpper); + Interop.JsGlobalization.ChangeCase(out exceptionMessage, _cultureName, src, srcLen, dstBuffer, dstBufferCapacity, toUpper); } if (!string.IsNullOrEmpty(exceptionMessage)) throw new Exception(exceptionMessage); } - } } diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c index e503ef32d0a7e..d07e106668c3e 100644 --- a/src/mono/wasm/runtime/corebindings.c +++ b/src/mono/wasm/runtime/corebindings.c @@ -45,6 +45,7 @@ extern void* mono_wasm_invoke_js_blazor (MonoString **exceptionMessage, void *ca // HybridGlobalization extern void mono_wasm_change_case_invariant(MonoString **exceptionMessage, const uint16_t* src, int32_t srcLength, uint16_t* dst, int32_t dstLength, mono_bool bToUpper); extern void mono_wasm_change_case(MonoString **exceptionMessage, MonoString **culture, const uint16_t* src, int32_t srcLength, uint16_t* dst, int32_t dstLength, mono_bool bToUpper); +extern int mono_wasm_compare_string(MonoString **exceptionMessage, MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options); void bindings_initialize_internals (void) { @@ -73,6 +74,7 @@ void bindings_initialize_internals (void) // Blazor specific custom routines - see dotnet_support.js for backing code mono_add_internal_call ("WebAssembly.JSInterop.InternalCalls::InvokeJS", mono_wasm_invoke_js_blazor); #endif /* ENABLE_LEGACY_JS_INTEROP */ - mono_add_internal_call ("System.Globalization.TextInfoInterop::ChangeCaseInvariantJS", mono_wasm_change_case_invariant); - mono_add_internal_call ("System.Globalization.TextInfoInterop::ChangeCaseJS", mono_wasm_change_case); + mono_add_internal_call ("Interop/JsGlobalization::ChangeCaseInvariant", mono_wasm_change_case_invariant); + mono_add_internal_call ("Interop/JsGlobalization::ChangeCase", mono_wasm_change_case); + mono_add_internal_call ("Interop/JsGlobalization::CompareString", mono_wasm_compare_string); } diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index e9baeb41de37b..19900b76cac87 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -99,6 +99,7 @@ let linked_functions = [ "mono_wasm_marshal_promise", "mono_wasm_change_case_invariant", "mono_wasm_change_case", + "mono_wasm_compare_string", "icudt68_dat", ]; diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts index f6d4ad1d06d40..ae0305551a42c 100644 --- a/src/mono/wasm/runtime/exports-linker.ts +++ b/src/mono/wasm/runtime/exports-linker.ts @@ -24,8 +24,9 @@ import { mono_wasm_typed_array_to_array_ref } from "./net6-legacy/js-to-cs"; import { mono_wasm_typed_array_from_ref } from "./net6-legacy/buffers"; import { mono_wasm_invoke_js_blazor, mono_wasm_invoke_js_with_args_ref, mono_wasm_get_object_property_ref, mono_wasm_set_object_property_ref, - mono_wasm_get_by_index_ref, mono_wasm_set_by_index_ref, mono_wasm_get_global_object_ref, mono_wasm_change_case_invariant, mono_wasm_change_case + mono_wasm_get_by_index_ref, mono_wasm_set_by_index_ref, mono_wasm_get_global_object_ref } from "./net6-legacy/method-calls"; +import { mono_wasm_change_case, mono_wasm_change_case_invariant, mono_wasm_compare_string } from "./net6-legacy/hybrid-globalization"; // the methods would be visible to EMCC linker // --- keep in sync with dotnet.cjs.lib.js --- @@ -95,6 +96,7 @@ export function export_linker(): any { mono_wasm_marshal_promise, mono_wasm_change_case_invariant, mono_wasm_change_case, + mono_wasm_compare_string, // threading exports, if threading is enabled ...mono_wasm_threads_exports, diff --git a/src/mono/wasm/runtime/net6-legacy/hybrid-globalization.ts b/src/mono/wasm/runtime/net6-legacy/hybrid-globalization.ts new file mode 100644 index 0000000000000..ae3ee2ac648f6 --- /dev/null +++ b/src/mono/wasm/runtime/net6-legacy/hybrid-globalization.ts @@ -0,0 +1,178 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { Module } from "../imports"; +import { mono_wasm_new_external_root } from "../roots"; +import {MonoString, MonoStringRef } from "../types"; +import { Int32Ptr } from "../types/emscripten"; +import { conv_string_root, js_string_to_mono_string_root } from "../strings"; +import { setU16 } from "../memory"; + +export function mono_wasm_change_case_invariant(exceptionMessage: Int32Ptr, src: number, srcLength: number, dst: number, dstLength: number, toUpper: number) : void{ + try{ + const input = get_utf16_string(src, srcLength); + let result = toUpper ? input.toUpperCase() : input.toLowerCase(); + // Unicode defines some codepoints which expand into multiple codepoints, + // originally we do not support this expansion + if (result.length > dstLength) + result = input; + + for (let i = 0; i < result.length; i++) + setU16(dst + i*2, result.charCodeAt(i)); + } + catch (ex: any) { + pass_exception_details(ex, exceptionMessage); + } +} + +export function mono_wasm_change_case(exceptionMessage: Int32Ptr, culture: MonoStringRef, src: number, srcLength: number, dst: number, destLength: number, toUpper: number) : void{ + const cultureRoot = mono_wasm_new_external_root(culture); + try{ + const cultureName = conv_string_root(cultureRoot); + if (!cultureName) + throw new Error("Cannot change case, the culture name is null."); + const input = get_utf16_string(src, srcLength); + let result = toUpper ? input.toLocaleUpperCase(cultureName) : input.toLocaleLowerCase(cultureName); + if (result.length > destLength) + result = input; + + for (let i = 0; i < destLength; i++) + setU16(dst + i*2, result.charCodeAt(i)); + } + catch (ex: any) { + pass_exception_details(ex, exceptionMessage); + } + finally { + cultureRoot.release(); + } +} + +export function mono_wasm_compare_string(exceptionMessage: Int32Ptr, culture: MonoStringRef, str1: number, str1Length: number, str2: number, str2Length: number, options: number) : number{ + const cultureRoot = mono_wasm_new_external_root(culture); + try{ + const cultureName = conv_string_root(cultureRoot); + const string1 = get_utf16_string(str1, str1Length); + const string2 = get_utf16_string(str2, str2Length); + const casePicker = (options & 0x1f); + const locale = cultureName ? cultureName : undefined; + const result = compare_strings(string1, string2, locale, casePicker); + if (result == -2) + throw new Error("$Invalid comparison option."); + return result; + } + catch (ex: any) { + pass_exception_details(ex, exceptionMessage); + return -2; + } + finally { + cultureRoot.release(); + } +} + +export function get_utf16_string(ptr: number, length: number): string{ + const view = new Uint16Array(Module.HEAPU16.buffer, ptr, length); + let string = ""; + for (let i = 0; i < length; i++) + string += String.fromCharCode(view[i]); + return string; +} + +export function pass_exception_details(ex: any, exceptionMessage: Int32Ptr){ + const exceptionJsString = ex.message + "\n" + ex.stack; + const exceptionRoot = mono_wasm_new_external_root(exceptionMessage); + js_string_to_mono_string_root(exceptionJsString, exceptionRoot); + exceptionRoot.release(); +} + +export function compare_strings(string1: string, string2: string, locale: string | undefined, casePicker: number) : number{ + switch (casePicker) + { + case 0: + // 0: None - default algorithm for the platform OR + // StringSort - since .Net 5 StringSort gives the same result as None, even for hyphen etc. + // does not work for "ja" + if (locale && locale.split("-")[0] === "ja") + return -2; + return string1.localeCompare(string2, locale); // a ≠ b, a ≠ á, a ≠ A + case 8: + // 8: IgnoreKanaType works only for "ja" + if (locale && locale.split("-")[0] !== "ja") + return -2; + return string1.localeCompare(string2, locale); // a ≠ b, a ≠ á, a ≠ A + case 1: + // 1: IgnoreCase + string1 = string1.toLocaleLowerCase(locale); + string2 = string2.toLocaleLowerCase(locale); + return string1.localeCompare(string2, locale); // a ≠ b, a ≠ á, a ≠ A + case 4: + case 12: + // 4: IgnoreSymbols + // 12: IgnoreKanaType | IgnoreSymbols + return string1.localeCompare(string2, locale, { ignorePunctuation: true }); // by default ignorePunctuation: false + case 5: + // 5: IgnoreSymbols | IgnoreCase + string1 = string1.toLocaleLowerCase(locale); + string2 = string2.toLocaleLowerCase(locale); + return string1.localeCompare(string2, locale, { ignorePunctuation: true }); // a ≠ b, a ≠ á, a ≠ A + case 9: + // 9: IgnoreKanaType | IgnoreCase + return string1.localeCompare(string2, locale, { sensitivity: "accent" }); // a ≠ b, a ≠ á, a = A + case 10: + // 10: IgnoreKanaType | IgnoreNonSpace + return string1.localeCompare(string2, locale, { sensitivity: "case" }); // a ≠ b, a = á, a ≠ A + case 11: + // 11: IgnoreKanaType | IgnoreNonSpace | IgnoreCase + return string1.localeCompare(string2, locale, { sensitivity: "base" }); // a ≠ b, a = á, a = A + case 13: + // 13: IgnoreKanaType | IgnoreCase | IgnoreSymbols + return string1.localeCompare(string2, locale, { sensitivity: "accent", ignorePunctuation: true }); // a ≠ b, a ≠ á, a = A + case 14: + // 14: IgnoreKanaType | IgnoreSymbols | IgnoreNonSpace + return string1.localeCompare(string2, locale, { sensitivity: "case", ignorePunctuation: true });// a ≠ b, a = á, a ≠ A + case 15: + // 15: IgnoreKanaType | IgnoreSymbols | IgnoreNonSpace | IgnoreCase + return string1.localeCompare(string2, locale, { sensitivity: "base", ignorePunctuation: true }); // a ≠ b, a = á, a = A + case 2: + case 3: + case 6: + case 7: + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + default: + // 2: IgnoreNonSpace + // 3: IgnoreNonSpace | IgnoreCase + // 6: IgnoreSymbols | IgnoreNonSpace + // 7: IgnoreSymbols | IgnoreNonSpace | IgnoreCase + // 16: IgnoreWidth + // 17: IgnoreWidth | IgnoreCase + // 18: IgnoreWidth | IgnoreNonSpace + // 19: IgnoreWidth | IgnoreNonSpace | IgnoreCase + // 20: IgnoreWidth | IgnoreSymbols + // 21: IgnoreWidth | IgnoreSymbols | IgnoreCase + // 22: IgnoreWidth | IgnoreSymbols | IgnoreNonSpace + // 23: IgnoreWidth | IgnoreSymbols | IgnoreNonSpace | IgnoreCase + // 24: IgnoreKanaType | IgnoreWidth + // 25: IgnoreKanaType | IgnoreWidth | IgnoreCase + // 26: IgnoreKanaType | IgnoreWidth | IgnoreNonSpace + // 27: IgnoreKanaType | IgnoreWidth | IgnoreNonSpace | IgnoreCase + // 28: IgnoreKanaType | IgnoreWidth | IgnoreSymbols + // 29: IgnoreKanaType | IgnoreWidth | IgnoreSymbols | IgnoreCase + // 30: IgnoreKanaType | IgnoreWidth | IgnoreSymbols | IgnoreNonSpace + // 31: IgnoreKanaType | IgnoreWidth | IgnoreSymbols | IgnoreNonSpace | IgnoreCase + return -2; + } +} diff --git a/src/mono/wasm/runtime/net6-legacy/method-calls.ts b/src/mono/wasm/runtime/net6-legacy/method-calls.ts index 8873fd3a9016a..f1a89755eb5c1 100644 --- a/src/mono/wasm/runtime/net6-legacy/method-calls.ts +++ b/src/mono/wasm/runtime/net6-legacy/method-calls.ts @@ -4,7 +4,7 @@ import { get_js_obj, mono_wasm_get_jsobj_from_js_handle } from "../gc-handles"; import { Module, runtimeHelpers, INTERNAL } from "../imports"; import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; -import { setU16, _release_temp_frame } from "../memory"; +import { _release_temp_frame } from "../memory"; import { mono_wasm_new_external_root, mono_wasm_new_root } from "../roots"; import { find_entry_point } from "../run"; import { conv_string_root, js_string_to_mono_string_root } from "../strings"; @@ -297,58 +297,3 @@ export function mono_wasm_invoke_js_blazor(exceptionMessage: Int32Ptr, callInfo: return 0; } } - -export function mono_wasm_change_case_invariant(exceptionMessage: Int32Ptr, src: number, srcLength: number, dst: number, dstLength: number, toUpper: number) : void{ - try{ - const input = get_uft16_string(src, srcLength); - let result = toUpper ? input.toUpperCase() : input.toLowerCase(); - // Unicode defines some codepoints which expand into multiple codepoints, - // originally we do not support this expansion - if (result.length > dstLength) - result = input; - - for (let i = 0; i < result.length; i++) - setU16(dst + i*2, result.charCodeAt(i)); - } - catch (ex: any) { - pass_exception_details(ex, exceptionMessage); - } -} - -export function mono_wasm_change_case(exceptionMessage: Int32Ptr, culture: MonoStringRef, src: number, srcLength: number, dst: number, destLength: number, toUpper: number) : void{ - const cultureRoot = mono_wasm_new_external_root(culture); - try{ - const cultureName = conv_string_root(cultureRoot); - if (!cultureName) - throw new Error("Cannot change case, the culture name is null."); - const input = get_uft16_string(src, srcLength); - let result = toUpper ? input.toLocaleUpperCase(cultureName) : input.toLocaleLowerCase(cultureName); - if (result.length > destLength) - result = input; - - for (let i = 0; i < destLength; i++) - setU16(dst + i*2, result.charCodeAt(i)); - } - catch (ex: any) { - pass_exception_details(ex, exceptionMessage); - } - finally { - cultureRoot.release(); - } -} - -function get_uft16_string (ptr: number, length: number): string{ - const view = new Uint16Array(Module.HEAPU16.buffer, ptr, length); - let string = ""; - for (let i = 0; i < length; i++) - string += String.fromCharCode(view[i]); - return string; -} - -function pass_exception_details(ex: any, exceptionMessage: Int32Ptr){ - const exceptionJsString = ex.message + "\n" + ex.stack; - const exceptionRoot = mono_wasm_new_root(); - js_string_to_mono_string_root(exceptionJsString, exceptionRoot); - exceptionRoot.copy_to_address(exceptionMessage); - exceptionRoot.release(); -}