// 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.CompilerServices; using System.Runtime.InteropServices; using System.Text; namespace System.Globalization { internal static class InvariantModeCasing { [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static char ToLower(char c) => CharUnicodeInfo.ToLower(c); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static char ToUpper(char c) => CharUnicodeInfo.ToUpper(c); internal static string ToLower(string s) { if (s.Length == 0) { return string.Empty; } ReadOnlySpan<char> source = s; int i = 0; while (i < s.Length) { if (char.IsHighSurrogate(source[i]) && i < s.Length - 1 && char.IsLowSurrogate(source[i + 1])) { SurrogateCasing.ToLower(source[i], source[i + 1], out char h, out char l); if (source[i] != h || source[i + 1] != l) { break; } i += 2; continue; } if (ToLower(source[i]) != source[i]) { break; } i++; } if (i >= s.Length) { return s; } string result = string.FastAllocateString(s.Length); var destination = new Span<char>(ref result.GetRawStringData(), result.Length); ReadOnlySpan<char> src = s; src.Slice(0, i).CopyTo(destination); ToLower(src.Slice(i), destination.Slice(i)); return result; } internal static string ToUpper(string s) { if (s.Length == 0) { return string.Empty; } ReadOnlySpan<char> source = s; int i = 0; while (i < s.Length) { if (char.IsHighSurrogate(source[i]) && i < s.Length - 1 && char.IsLowSurrogate(source[i + 1])) { SurrogateCasing.ToUpper(source[i], source[i + 1], out char h, out char l); if (source[i] != h || source[i + 1] != l) { break; } i += 2; continue; } if (ToUpper(source[i]) != source[i]) { break; } i++; } if (i >= s.Length) { return s; } string result = string.FastAllocateString(s.Length); var destination = new Span<char>(ref result.GetRawStringData(), result.Length); ReadOnlySpan<char> src = s; src.Slice(0, i).CopyTo(destination); ToUpper(src.Slice(i), destination.Slice(i)); return result; } internal static void ToUpper(ReadOnlySpan<char> source, Span<char> destination) { Debug.Assert(GlobalizationMode.Invariant); Debug.Assert(source.Length <= destination.Length); for (int i = 0; i < source.Length; i++) { char c = source[i]; if (char.IsHighSurrogate(c) && i < source.Length - 1) { char cl = source[i + 1]; if (char.IsLowSurrogate(cl)) { // well formed surrogates SurrogateCasing.ToUpper(c, cl, out char h, out char l); destination[i] = h; destination[i + 1] = l; i++; // skip the low surrogate continue; } } destination[i] = ToUpper(c); } } internal static void ToLower(ReadOnlySpan<char> source, Span<char> destination) { Debug.Assert(GlobalizationMode.Invariant); Debug.Assert(source.Length <= destination.Length); for (int i = 0; i < source.Length; i++) { char c = source[i]; if (char.IsHighSurrogate(c) && i < source.Length - 1) { char cl = source[i + 1]; if (char.IsLowSurrogate(cl)) { // well formed surrogates SurrogateCasing.ToLower(c, cl, out char h, out char l); destination[i] = h; destination[i + 1] = l; i++; // skip the low surrogate continue; } } destination[i] = ToLower(c); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static (uint, int) GetScalar(ref char source, int index, int length) { char charA = source; if (!char.IsHighSurrogate(charA) || index >= length - 1) { return ((uint)charA, 1); } char charB = Unsafe.Add(ref source, 1); if (!char.IsLowSurrogate(charB)) { return ((uint)charA, 1); } return (UnicodeUtility.GetScalarFromUtf16SurrogatePair(charA, charB), 2); } internal static int CompareStringIgnoreCase(ref char strA, int lengthA, ref char strB, int lengthB) { Debug.Assert(GlobalizationMode.Invariant); int length = Math.Min(lengthA, lengthB); ref char charA = ref strA; ref char charB = ref strB; int index = 0; while (index < length) { (uint codePointA, int codePointLengthA) = GetScalar(ref charA, index, lengthA); (uint codePointB, int codePointLengthB) = GetScalar(ref charB, index, lengthB); if (codePointA == codePointB) { Debug.Assert(codePointLengthA == codePointLengthB); index += codePointLengthA; charA = ref Unsafe.Add(ref charA, codePointLengthA); charB = ref Unsafe.Add(ref charB, codePointLengthB); continue; } uint aUpper = CharUnicodeInfo.ToUpper(codePointA); uint bUpper = CharUnicodeInfo.ToUpper(codePointB); if (aUpper == bUpper) { Debug.Assert(codePointLengthA == codePointLengthB); index += codePointLengthA; charA = ref Unsafe.Add(ref charA, codePointLengthA); charB = ref Unsafe.Add(ref charB, codePointLengthB); continue; } return (int)aUpper - (int)bUpper; } return lengthA - lengthB; } internal static unsafe int IndexOfIgnoreCase(ReadOnlySpan<char> source, ReadOnlySpan<char> value) { Debug.Assert(value.Length > 0); Debug.Assert(value.Length <= source.Length); Debug.Assert(GlobalizationMode.Invariant); fixed (char* pSource = &MemoryMarshal.GetReference(source)) fixed (char* pValue = &MemoryMarshal.GetReference(value)) { char* pSourceLimit = pSource + (source.Length - value.Length); char* pValueLimit = pValue + value.Length - 1; char* pCurrentSource = pSource; while (pCurrentSource <= pSourceLimit) { char *pVal = pValue; char *pSrc = pCurrentSource; while (pVal <= pValueLimit) { if (!char.IsHighSurrogate(*pVal) || pVal == pValueLimit) { if (*pVal != *pSrc && ToUpper(*pVal) != ToUpper(*pSrc)) break; // no match pVal++; pSrc++; continue; } if (char.IsHighSurrogate(*pSrc) && char.IsLowSurrogate(*(pSrc + 1)) && char.IsLowSurrogate(*(pVal + 1))) { // Well formed surrogates // both the source and the Value have well-formed surrogates. if (!SurrogateCasing.Equal(*pSrc, *(pSrc + 1), *pVal, *(pVal + 1))) break; // no match pSrc += 2; pVal += 2; continue; } if (*pVal != *pSrc) break; // no match pSrc++; pVal++; } if (pVal > pValueLimit) { // Found match. return (int)(pCurrentSource - pSource); } pCurrentSource++; } return -1; } } internal static unsafe int LastIndexOfIgnoreCase(ReadOnlySpan<char> source, ReadOnlySpan<char> value) { Debug.Assert(value.Length > 0); Debug.Assert(value.Length <= source.Length); Debug.Assert(GlobalizationMode.Invariant); fixed (char* pSource = &MemoryMarshal.GetReference(source)) fixed (char* pValue = &MemoryMarshal.GetReference(value)) { char* pValueLimit = pValue + value.Length - 1; char* pCurrentSource = pSource + (source.Length - value.Length); while (pCurrentSource >= pSource) { char *pVal = pValue; char *pSrc = pCurrentSource; while (pVal <= pValueLimit) { if (!char.IsHighSurrogate(*pVal) || pVal == pValueLimit) { if (*pVal != *pSrc && ToUpper(*pVal) != ToUpper(*pSrc)) break; // no match pVal++; pSrc++; continue; } if (char.IsHighSurrogate(*pSrc) && char.IsLowSurrogate(*(pSrc + 1)) && char.IsLowSurrogate(*(pVal + 1))) { // Well formed surrogates // both the source and the Value have well-formed surrogates. if (!SurrogateCasing.Equal(*pSrc, *(pSrc + 1), *pVal, *(pVal + 1))) break; // no match pSrc += 2; pVal += 2; continue; } if (*pVal != *pSrc) break; // no match pSrc++; pVal++; } if (pVal > pValueLimit) { // Found match. return (int)(pCurrentSource - pSource); } pCurrentSource--; } return -1; } } } }