Skip to content

Commit 9d4c65d

Browse files
matouskozaktarekghCopilot
authored
[iOS] Implement IgnoreSymbols for CompreInfo in managed code (#120462)
Implementation of `CompareOptions.IgnoreSymbols` for `IndexOf`, `IsPrefix`, `IsSuffix`, `Compare` on iOS. Because ObjectiveC string APIs (https://developer.apple.com/documentation/foundation/nsstring/compareoptions?language=objc) don't provide a direct alternative to `IgnoreSymbols` option, we filter the symbols from the strings in the managed code and after returning from native, we re-calculate the original index and length. The implementation works in a following: 1. Preprocess the source and search strings by removing the symbols. 2. Invoke the native API for comparison. 3. Map the range from preprocessed source string back to the original string to get the index and length. --------- Co-authored-by: Tarek Mahmoud Sayed <[email protected]> Co-authored-by: Tarek Mahmoud Sayed <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 952e147 commit 9d4c65d

File tree

9 files changed

+464
-103
lines changed

9 files changed

+464
-103
lines changed

docs/design/features/globalization-hybrid-mode.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Due to these differences, the exact result of string compariso on Apple platform
3030

3131
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).
3232

33-
- `IgnoreSymbols` is not supported because there is no equivalent in native api. Throws `PlatformNotSupportedException`.
33+
- `IgnoreSymbols` is supported by filtering ignorable symbols on the managed side before invoking the native API.
3434

3535
- `IgnoreKanaType` is implemented using [`kCFStringTransformHiraganaKatakana`](https://developer.apple.com/documentation/corefoundation/kcfstringtransformhiraganakatakana?language=objc) then comparing strings.
3636

@@ -71,10 +71,6 @@ The number of `CompareOptions` and `NSStringCompareOptions` combinations are lim
7171

7272
`CompareOptions.IgnoreWidth` is mapped to `NSStringCompareOptions.NSWidthInsensitiveSearch | NSStringCompareOptions.NSLiteralSearch`
7373

74-
- All combinations that contain below `CompareOptions` always throw `PlatformNotSupportedException`:
75-
76-
`IgnoreSymbols`
77-
7874
## String starts with / ends with
7975

8076
Affected public APIs:
@@ -91,7 +87,7 @@ Apple Native API does not expose locale-sensitive endsWith/startsWith function.
9187

9288
- `IgnoreSymbols`
9389

94-
As there is no IgnoreSymbols equivalent in NSStringCompareOptions all `CompareOptions` combinations that include `IgnoreSymbols` throw `PlatformNotSupportedException`
90+
Supported by filtering ignorable symbols on the managed side prior to comparison using native API.
9591

9692
## String indexing
9793

@@ -129,7 +125,7 @@ Not covered case:
129125

130126
- `IgnoreSymbols`
131127

132-
As there is no IgnoreSymbols equivalent in NSStringCompareOptions all `CompareOptions` combinations that include `IgnoreSymbols` throw `PlatformNotSupportedException`
128+
Supported by filtering ignorable symbols on the managed side prior to comparison using native API.
133129

134130
- Some letters consist of more than one grapheme.
135131

src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,15 @@ private unsafe int IcuIndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char>
7878
}
7979
else
8080
{
81+
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
82+
if (GlobalizationMode.Hybrid)
83+
return IndexOfCoreNative(target, source, options, fromBeginning, matchLengthPtr);
84+
#endif
8185
// GetReference may return nullptr if the input span is defaulted. The native layer handles
8286
// this appropriately; no workaround is needed on the managed side.
83-
8487
fixed (char* pSource = &MemoryMarshal.GetReference(source))
8588
fixed (char* pTarget = &MemoryMarshal.GetReference(target))
8689
{
87-
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
88-
if (GlobalizationMode.Hybrid)
89-
return IndexOfCoreNative(pTarget, target.Length, pSource, source.Length, options, fromBeginning, matchLengthPtr);
90-
#endif
9190
if (fromBeginning)
9291
return Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options, matchLengthPtr);
9392
else
@@ -207,7 +206,7 @@ private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, Rea
207206
InteropCall:
208207
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
209208
if (GlobalizationMode.Hybrid)
210-
return IndexOfCoreNative(b, target.Length, a, source.Length, options, fromBeginning, matchLengthPtr);
209+
return IndexOfCoreNative(target, source, options, fromBeginning, matchLengthPtr);
211210
#endif
212211
if (fromBeginning)
213212
return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
@@ -301,7 +300,7 @@ private unsafe int IndexOfOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<
301300
InteropCall:
302301
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
303302
if (GlobalizationMode.Hybrid)
304-
return IndexOfCoreNative(b, target.Length, a, source.Length, options, fromBeginning, matchLengthPtr);
303+
return IndexOfCoreNative(target, source, options, fromBeginning, matchLengthPtr);
305304
#endif
306305
if (fromBeginning)
307306
return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
@@ -328,13 +327,13 @@ private unsafe bool IcuStartsWith(ReadOnlySpan<char> source, ReadOnlySpan<char>
328327
}
329328
else
330329
{
330+
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
331+
if (GlobalizationMode.Hybrid)
332+
return NativeStartsWith(prefix, source, options);
333+
#endif
331334
fixed (char* pSource = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferenced)
332335
fixed (char* pPrefix = &MemoryMarshal.GetReference(prefix))
333336
{
334-
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
335-
if (GlobalizationMode.Hybrid)
336-
return NativeStartsWith(pPrefix, prefix.Length, pSource, source.Length, options);
337-
#endif
338337
return Interop.Globalization.StartsWith(_sortHandle, pPrefix, prefix.Length, pSource, source.Length, options, matchLengthPtr);
339338
}
340339
}
@@ -416,7 +415,7 @@ private unsafe bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source,
416415
InteropCall:
417416
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
418417
if (GlobalizationMode.Hybrid)
419-
return NativeStartsWith(bp, prefix.Length, ap, source.Length, options);
418+
return NativeStartsWith(prefix, source, options);
420419
#endif
421420
return Interop.Globalization.StartsWith(_sortHandle, bp, prefix.Length, ap, source.Length, options, matchLengthPtr);
422421
}
@@ -488,7 +487,7 @@ private unsafe bool StartsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlyS
488487
InteropCall:
489488
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
490489
if (GlobalizationMode.Hybrid)
491-
return NativeStartsWith(bp, prefix.Length, ap, source.Length, options);
490+
return NativeStartsWith(prefix, source, options);
492491
#endif
493492
return Interop.Globalization.StartsWith(_sortHandle, bp, prefix.Length, ap, source.Length, options, matchLengthPtr);
494493
}
@@ -512,13 +511,13 @@ private unsafe bool IcuEndsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> su
512511
}
513512
else
514513
{
514+
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
515+
if (GlobalizationMode.Hybrid)
516+
return NativeEndsWith(suffix, source, options);
517+
#endif
515518
fixed (char* pSource = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferenced)
516519
fixed (char* pSuffix = &MemoryMarshal.GetReference(suffix))
517520
{
518-
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
519-
if (GlobalizationMode.Hybrid)
520-
return NativeEndsWith(pSuffix, suffix.Length, pSource, source.Length, options);
521-
#endif
522521
return Interop.Globalization.EndsWith(_sortHandle, pSuffix, suffix.Length, pSource, source.Length, options, matchLengthPtr);
523522
}
524523
}
@@ -601,7 +600,7 @@ private unsafe bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, R
601600
InteropCall:
602601
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
603602
if (GlobalizationMode.Hybrid)
604-
return NativeEndsWith(bp, suffix.Length, ap, source.Length, options);
603+
return NativeEndsWith(suffix, source, options);
605604
#endif
606605
return Interop.Globalization.EndsWith(_sortHandle, bp, suffix.Length, ap, source.Length, options, matchLengthPtr);
607606
}
@@ -673,7 +672,7 @@ private unsafe bool EndsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpa
673672
InteropCall:
674673
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
675674
if (GlobalizationMode.Hybrid)
676-
return NativeEndsWith(bp, suffix.Length, ap, source.Length, options);
675+
return NativeEndsWith(suffix, source, options);
677676
#endif
678677
return Interop.Globalization.EndsWith(_sortHandle, bp, suffix.Length, ap, source.Length, options, matchLengthPtr);
679678
}

0 commit comments

Comments
 (0)