< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 320
Coverable lines: 320
Total lines: 988
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 234
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: .cctor()100%110%
File 1: .ctor(...)0%220%
File 1: .ctor(...)100%110%
File 1: System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(...)100%110%
File 1: Clone()100%110%
File 1: ReadOnly(...)0%220%
File 1: VerifyWritable()0%220%
File 1: SetReadOnlyState(...)100%110%
File 1: ToLower(...)0%660%
File 1: ToLowerInvariant(...)0%440%
File 1: ToLower(...)100%110%
File 1: ToLowerInvariant(...)100%110%
File 1: ToLower(...)100%110%
File 1: ChangeCase(...)100%110%
File 1: ToUpperOrdinal(...)0%660%
File 1: ToLowerOrdinal(...)0%880%
File 1: PreserveOrdinalLowerCasingClass(...)0%440%
File 1: ChangeCaseToLower(...)100%110%
File 1: ChangeCaseToUpper(...)100%110%
File 1: ChangeCaseCommon(...)0%18180%
File 1: ChangeCaseCommon(...)0%38380%
File 1: ToLowerAsciiInvariant(...)0%10100%
File 1: ToLowerAsciiInvariant(...)0%220%
File 1: ToUpper(...)0%660%
File 1: ToUpperInvariant(...)0%440%
File 1: ToUpper(...)100%110%
File 1: ToUpperInvariant(...)100%110%
File 1: ToUpper(...)100%110%
File 1: ToUpperAsciiInvariant(...)0%220%
File 1: ToLower(...)0%220%
File 1: ToUpper(...)0%220%
File 1: PopulateIsAsciiCasingSameAsInvariant()0%220%
File 1: Equals(...)0%220%
File 1: GetHashCode()100%110%
File 1: ToString()100%110%
File 1: ToTitleCase(...)0%44440%
File 1: AddNonLetter(...)0%440%
File 1: AddTitlecaseLetter(...)0%22220%
File 1: ChangeCaseCore(...)0%220%
File 1: IsWordSeparator(...)100%110%
File 1: IsApostrophe(...)0%10100%
File 1: IsLetterCategory(...)0%880%
File 2: NeedsTurkishCasing(...)100%110%
File 2: IcuChangeCase(...)0%880%
File 3: NlsChangeCase(...)0%880%
File 3: IsInvariantLocale(...)100%110%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Runtime.CompilerServices;
 8using System.Runtime.InteropServices;
 9using System.Runtime.Serialization;
 10using System.Text;
 11using System.Text.Unicode;
 12
 13namespace System.Globalization
 14{
 15    /// <summary>
 16    /// This Class defines behaviors specific to a writing system.
 17    /// A writing system is the collection of scripts and orthographic rules
 18    /// required to represent a language as text.
 19    /// </summary>
 20    public sealed partial class TextInfo : ICloneable, IDeserializationCallback
 21    {
 22        private bool _isReadOnly;
 23
 24        private readonly string _cultureName;
 25        private readonly CultureData _cultureData;
 26
 027        private bool HasEmptyCultureName { get { return _cultureName.Length == 0; } }
 28
 29        // // Name of the text info we're using (ie: _cultureData.TextInfoName)
 30        private readonly string _textInfoName;
 31
 32        private NullableBool _isAsciiCasingSameAsInvariant;
 33
 34        // Invariant text info
 035        internal static readonly TextInfo Invariant = new TextInfo(CultureData.Invariant, readOnly: true) { _isAsciiCasi
 36
 037        internal TextInfo(CultureData cultureData)
 38        {
 39            // This is our primary data source, we don't need most of the rest of this
 040            _cultureData = cultureData;
 041            _cultureName = _cultureData.CultureName;
 042            _textInfoName = _cultureData.TextInfoName;
 43
 044            if (GlobalizationMode.UseNls)
 45            {
 046                _sortHandle = CompareInfo.NlsGetSortHandle(_textInfoName);
 47            }
 048        }
 49
 50        private TextInfo(CultureData cultureData, bool readOnly)
 051            : this(cultureData)
 52        {
 053            SetReadOnlyState(readOnly);
 054        }
 55
 56        void IDeserializationCallback.OnDeserialization(object? sender)
 57        {
 058            throw new PlatformNotSupportedException();
 59        }
 60
 061        public int ANSICodePage => _cultureData.ANSICodePage;
 62
 063        public int OEMCodePage => _cultureData.OEMCodePage;
 64
 065        public int MacCodePage => _cultureData.MacCodePage;
 66
 067        public int EBCDICCodePage => _cultureData.EBCDICCodePage;
 68
 69        // Just use the LCID from our text info name
 070        public int LCID => CultureInfo.GetCultureInfo(_textInfoName).LCID;
 71
 072        public string CultureName => _textInfoName;
 73
 074        public bool IsReadOnly => _isReadOnly;
 75
 76        public object Clone()
 77        {
 078            object o = MemberwiseClone();
 079            ((TextInfo)o).SetReadOnlyState(false);
 080            return o;
 81        }
 82
 83        /// <summary>
 84        /// Create a cloned readonly instance or return the input one if it is
 85        /// readonly.
 86        /// </summary>
 87        public static TextInfo ReadOnly(TextInfo textInfo)
 88        {
 089            ArgumentNullException.ThrowIfNull(textInfo);
 90
 091            if (textInfo.IsReadOnly)
 92            {
 093                return textInfo;
 94            }
 95
 096            TextInfo clonedTextInfo = (TextInfo)(textInfo.MemberwiseClone());
 097            clonedTextInfo.SetReadOnlyState(true);
 098            return clonedTextInfo;
 99        }
 100
 101        private void VerifyWritable()
 102        {
 0103            if (_isReadOnly)
 104            {
 0105                throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 106            }
 0107        }
 108
 109        internal void SetReadOnlyState(bool readOnly)
 110        {
 0111            _isReadOnly = readOnly;
 0112        }
 113
 114        /// <summary>
 115        /// Returns the string used to separate items in a list.
 116        /// </summary>
 117        public string ListSeparator
 118        {
 0119            get => field ??= _cultureData.ListSeparator;
 120            set
 121            {
 0122                ArgumentNullException.ThrowIfNull(value);
 123
 0124                VerifyWritable();
 0125                field = value;
 0126            }
 127        }
 128
 129        /// <summary>
 130        /// Converts the character or string to lower case.  Certain locales
 131        /// have different casing semantics from the file systems in Win32.
 132        /// </summary>
 133        public char ToLower(char c)
 134        {
 0135            if (GlobalizationMode.Invariant)
 136            {
 0137                return InvariantModeCasing.ToLower(c);
 138            }
 139
 0140            if (UnicodeUtility.IsAsciiCodePoint(c) && IsAsciiCasingSameAsInvariant)
 141            {
 0142                return ToLowerAsciiInvariant(c);
 143            }
 144
 0145            return ChangeCase(c, toUpper: false);
 146        }
 147
 148        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 149        internal static char ToLowerInvariant(char c)
 150        {
 0151            if (UnicodeUtility.IsAsciiCodePoint(c))
 152            {
 0153                return ToLowerAsciiInvariant(c);
 154            }
 155
 0156            if (GlobalizationMode.Invariant)
 157            {
 0158                return InvariantModeCasing.ToLower(c);
 159            }
 160
 0161            return Invariant.ChangeCase(c, toUpper: false);
 162        }
 163
 164        public string ToLower(string str)
 165        {
 0166            ArgumentNullException.ThrowIfNull(str);
 0167            return ChangeCaseCommon<ToLowerConversion>(this, str);
 168        }
 169
 170        internal static string ToLowerInvariant(string str)
 171        {
 0172            ArgumentNullException.ThrowIfNull(str);
 0173            return ChangeCaseCommon<ToLowerConversion>(null, str);
 174        }
 175
 176        internal void ToLower(ReadOnlySpan<char> source, Span<char> destination)
 177        {
 0178            ChangeCaseCommon<ToLowerConversion>(this, source, destination);
 0179        }
 180
 181        private unsafe char ChangeCase(char c, bool toUpper)
 182        {
 0183            Debug.Assert(!GlobalizationMode.Invariant);
 0184            char dst = default;
 0185            ChangeCaseCore(&c, 1, &dst, 1, toUpper);
 0186            return dst;
 187        }
 188
 189        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 190        internal static char ToUpperOrdinal(char c)
 191        {
 0192            if (GlobalizationMode.Invariant)
 193            {
 0194                return InvariantModeCasing.ToUpper(c);
 195            }
 196
 0197            if (GlobalizationMode.UseNls)
 198            {
 0199                return char.IsAscii(c)
 0200                    ? ToUpperAsciiInvariant(c)
 0201                    : Invariant.ChangeCase(c, toUpper: true);
 202            }
 203
 0204            return OrdinalCasing.ToUpper(c);
 205        }
 206
 207        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 208        internal static char ToLowerOrdinal(char c)
 209        {
 0210            if (GlobalizationMode.Invariant)
 211            {
 0212                return char.IsAscii(c)
 0213                    ? ToLowerAsciiInvariant(c)
 0214                    : PreserveOrdinalLowerCasingClass(c, InvariantModeCasing.ToLower(c));
 215            }
 216
 0217            if (GlobalizationMode.UseNls)
 218            {
 0219                return char.IsAscii(c)
 0220                    ? ToLowerAsciiInvariant(c)
 0221                    : PreserveOrdinalLowerCasingClass(c, Invariant.ChangeCase(c, toUpper: false));
 222            }
 223
 0224            return OrdinalCasing.ToLower(c);
 225        }
 226
 227        // Ordinal lower casing must never move a character out of its ordinal upper-casing class, otherwise it
 228        // would stop being consistent with OrdinalIgnoreCase (for example the Kelvin, Ohm and Angstrom signs). The
 229        // ICU ordinal table encodes this directly, but invariant and NLS simple lowering do not, so keep the original
 230        // character whenever its simple lower mapping would change its ordinal upper-casing form.
 231        private static char PreserveOrdinalLowerCasingClass(char c, char lower) =>
 0232            lower == c || ToUpperOrdinal(lower) == ToUpperOrdinal(c) ? lower : c;
 233
 234        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 235        internal void ChangeCaseToLower(ReadOnlySpan<char> source, Span<char> destination)
 236        {
 0237            Debug.Assert(destination.Length >= source.Length);
 0238            ChangeCaseCommon<ToLowerConversion>(this, source, destination);
 0239        }
 240
 241        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 242        internal void ChangeCaseToUpper(ReadOnlySpan<char> source, Span<char> destination)
 243        {
 0244            Debug.Assert(destination.Length >= source.Length);
 0245            ChangeCaseCommon<ToUpperConversion>(this, source, destination);
 0246        }
 247
 248        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 249        private static unsafe void ChangeCaseCommon<TConversion>(TextInfo? instance, ReadOnlySpan<char> source, Span<cha
 250        {
 0251            Debug.Assert(typeof(TConversion) == typeof(ToUpperConversion) || typeof(TConversion) == typeof(ToLowerConver
 252
 0253            if (source.IsEmpty)
 254            {
 0255                return;
 256            }
 257
 0258            bool toUpper = typeof(TConversion) == typeof(ToUpperConversion); // JIT will treat this as a constant in rel
 0259            int charsConsumed = 0;
 260
 261            // instance being null indicates the invariant culture where IsAsciiCasingSameAsInvariant is always true.
 0262            if (instance == null || instance.IsAsciiCasingSameAsInvariant)
 263            {
 0264                OperationStatus operationStatus = toUpper
 0265                    ? Ascii.ToUpper(source, destination, out charsConsumed)
 0266                    : Ascii.ToLower(source, destination, out charsConsumed);
 267
 0268                if (operationStatus != OperationStatus.InvalidData)
 269                {
 0270                    Debug.Assert(operationStatus == OperationStatus.Done);
 0271                    return;
 272                }
 273            }
 274
 0275            if (GlobalizationMode.Invariant)
 276            {
 0277                if (toUpper)
 278                {
 0279                    InvariantModeCasing.ToUpper(source, destination);
 280                }
 281                else
 282                {
 0283                    InvariantModeCasing.ToLower(source, destination);
 284                }
 0285                return;
 286            }
 287
 288            // instance being null means it's Invariant
 0289            instance ??= Invariant;
 290
 0291            fixed (char* pSource = &MemoryMarshal.GetReference(source))
 0292            fixed (char* pDestination = &MemoryMarshal.GetReference(destination))
 293            {
 0294                instance.ChangeCaseCore(pSource + charsConsumed, source.Length - charsConsumed,
 0295                    pDestination + charsConsumed, destination.Length - charsConsumed, toUpper);
 296            }
 0297        }
 298
 299        private static unsafe string ChangeCaseCommon<TConversion>(TextInfo? instance, string source) where TConversion 
 300        {
 0301            Debug.Assert(typeof(TConversion) == typeof(ToUpperConversion) || typeof(TConversion) == typeof(ToLowerConver
 0302            bool toUpper = typeof(TConversion) == typeof(ToUpperConversion); // JIT will treat this as a constant in rel
 303
 0304            Debug.Assert(source != null);
 305
 306            // If the string is empty, we're done.
 0307            if (source.Length == 0)
 308            {
 0309                return string.Empty;
 310            }
 311
 0312            fixed (char* pSource = source)
 313            {
 0314                nuint currIdx = 0; // in chars
 315
 316                // If this culture's casing for ASCII is the same as invariant, try to take
 317                // a fast path that'll work in managed code and ASCII rather than calling out
 318                // to the OS for culture-aware casing.
 319                //
 320                // instance being null indicates the invariant culture where IsAsciiCasingSameAsInvariant is always true
 0321                if (instance == null || instance.IsAsciiCasingSameAsInvariant)
 322                {
 323                    // Read 2 chars (one 32-bit integer) at a time
 324
 0325                    if (source.Length >= 2)
 326                    {
 0327                        nuint lastIndexWhereCanReadTwoChars = (uint)source.Length - 2;
 328                        do
 329                        {
 330                            // See the comments in ChangeCaseCommon<TConversion>(ROS<char>, Span<char>) for a full expla
 331
 0332                            uint tempValue = Unsafe.ReadUnaligned<uint>(pSource + currIdx);
 0333                            if (!Utf16Utility.AllCharsInUInt32AreAscii(tempValue))
 334                            {
 335                                goto NotAscii;
 336                            }
 0337                            if ((toUpper) ? Utf16Utility.UInt32ContainsAnyLowercaseAsciiChar(tempValue) : Utf16Utility.U
 338                            {
 339                                goto AsciiMustChangeCase;
 340                            }
 341
 0342                            currIdx += 2;
 0343                        } while (currIdx <= lastIndexWhereCanReadTwoChars);
 344                    }
 345
 346                    // If there's a single character left to convert, do it now.
 0347                    if ((source.Length & 1) != 0)
 348                    {
 0349                        uint tempValue = pSource[currIdx];
 0350                        if (tempValue > 0x7Fu)
 351                        {
 352                            goto NotAscii;
 353                        }
 0354                        if ((toUpper) ? ((tempValue - 'a') <= (uint)('z' - 'a')) : ((tempValue - 'A') <= (uint)('Z' - 'A
 355                        {
 356                            goto AsciiMustChangeCase;
 357                        }
 358                    }
 359
 360                    // We got through all characters without finding anything that needed to change - done!
 0361                    return source;
 362
 363                AsciiMustChangeCase:
 364                    {
 365                        // We reached ASCII data that requires a case change.
 366                        // This will necessarily allocate a new string, but let's try to stay within the managed (non-lo
 367                        // conversion code path if we can.
 368
 0369                        string result = string.FastAllocateString(source.Length); // changing case uses simple folding: 
 370
 371                        // copy existing known-good data into the result
 0372                        Span<char> resultSpan = new Span<char>(ref result.GetRawStringData(), result.Length);
 0373                        source.AsSpan(0, (int)currIdx).CopyTo(resultSpan);
 374
 375                        // and re-run the fast span-based logic over the remainder of the data
 0376                        ChangeCaseCommon<TConversion>(instance, source.AsSpan((int)currIdx), resultSpan.Slice((int)currI
 0377                        return result;
 378                    }
 379                }
 380
 381            NotAscii:
 382                {
 0383                    if (GlobalizationMode.Invariant)
 384                    {
 0385                        return toUpper ? InvariantModeCasing.ToUpper(source) : InvariantModeCasing.ToLower(source);
 386                    }
 387
 388                    // We reached non-ASCII data *or* the requested culture doesn't map ASCII data the same way as the i
 389                    // In either case we need to fall back to the localization tables.
 390
 0391                    string result = string.FastAllocateString(source.Length); // changing case uses simple folding: does
 392
 0393                    if (currIdx > 0)
 394                    {
 395                        // copy existing known-good data into the result
 0396                        Span<char> resultSpan = new Span<char>(ref result.GetRawStringData(), result.Length);
 0397                        source.AsSpan(0, (int)currIdx).CopyTo(resultSpan);
 398                    }
 399
 400                    // instance being null means it's Invariant
 0401                    instance ??= Invariant;
 402
 403                    // and run the culture-aware logic over the remainder of the data
 0404                    fixed (char* pResult = result)
 405                    {
 0406                        instance.ChangeCaseCore(pSource + currIdx, source.Length - (int)currIdx, pResult + currIdx, resu
 407                    }
 0408                    return result;
 409                }
 410            }
 411        }
 412
 413        internal static unsafe string ToLowerAsciiInvariant(string s)
 414        {
 0415            if (s.Length == 0)
 416            {
 0417                return string.Empty;
 418            }
 419
 0420            int i = s.AsSpan().IndexOfAnyInRange('A', 'Z');
 0421            if (i < 0)
 422            {
 0423                return s;
 424            }
 425
 0426            fixed (char* pSource = s)
 427            {
 0428                string result = string.FastAllocateString(s.Length);
 0429                fixed (char* pResult = result)
 430                {
 0431                    s.AsSpan(0, i).CopyTo(new Span<char>(pResult, result.Length));
 432
 0433                    pResult[i] = (char)(pSource[i] | 0x20);
 0434                    i++;
 435
 0436                    while (i < s.Length)
 437                    {
 0438                        pResult[i] = ToLowerAsciiInvariant(pSource[i]);
 0439                        i++;
 440                    }
 441                }
 442
 0443                return result;
 444            }
 445        }
 446
 447        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 448        private static char ToLowerAsciiInvariant(char c)
 449        {
 0450            if (char.IsAsciiLetterUpper(c))
 451            {
 452                // on x86, extending BYTE -> DWORD is more efficient than WORD -> DWORD
 0453                c = (char)(byte)(c | 0x20);
 454            }
 0455            return c;
 456        }
 457
 458        /// <summary>
 459        /// Converts the character or string to upper case.  Certain locales
 460        /// have different casing semantics from the file systems in Win32.
 461        /// </summary>
 462        public char ToUpper(char c)
 463        {
 0464            if (GlobalizationMode.Invariant)
 465            {
 0466                return InvariantModeCasing.ToUpper(c);
 467            }
 468
 0469            if (UnicodeUtility.IsAsciiCodePoint(c) && IsAsciiCasingSameAsInvariant)
 470            {
 0471                return ToUpperAsciiInvariant(c);
 472            }
 473
 0474            return ChangeCase(c, toUpper: true);
 475        }
 476
 477        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 478        internal static char ToUpperInvariant(char c)
 479        {
 0480            if (UnicodeUtility.IsAsciiCodePoint(c))
 481            {
 0482                return ToUpperAsciiInvariant(c);
 483            }
 484
 0485            if (GlobalizationMode.Invariant)
 486            {
 0487                return InvariantModeCasing.ToUpper(c);
 488            }
 489
 0490            return Invariant.ChangeCase(c, toUpper: true);
 491        }
 492
 493        public string ToUpper(string str)
 494        {
 0495            ArgumentNullException.ThrowIfNull(str);
 0496            return ChangeCaseCommon<ToUpperConversion>(this, str);
 497        }
 498
 499        internal static string ToUpperInvariant(string str)
 500        {
 0501            ArgumentNullException.ThrowIfNull(str);
 0502            return ChangeCaseCommon<ToUpperConversion>(null, str);
 503        }
 504
 505        internal void ToUpper(ReadOnlySpan<char> source, Span<char> destination)
 506        {
 0507            ChangeCaseCommon<ToUpperConversion>(this, source, destination);
 0508        }
 509
 510        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 511        internal static char ToUpperAsciiInvariant(char c)
 512        {
 0513            if (char.IsAsciiLetterLower(c))
 514            {
 0515                c = (char)(c & 0x5F); // = low 7 bits of ~0x20
 516            }
 0517            return c;
 518        }
 519
 520        /// <summary>
 521        /// Converts the specified rune to lowercase.
 522        /// </summary>
 523        /// <param name="value">The rune to convert to lowercase.</param>
 524        /// <returns>The specified rune converted to lowercase.</returns>
 525        public unsafe Rune ToLower(Rune value)
 526        {
 527            // Convert rune to span
 0528            ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);
 529
 530            // Change span to lower and convert to rune
 0531            if (valueChars.Length == 2)
 532            {
 0533                Span<char> lowerChars = ['\0', '\0'];
 0534                ToLower(valueChars, lowerChars);
 0535                return new Rune(lowerChars[0], lowerChars[1]);
 536            }
 537
 0538            char lowerChar = ToLower(valueChars[0]);
 0539            return new Rune(lowerChar);
 540        }
 541
 542        /// <summary>
 543        /// Converts the specified rune to uppercase.
 544        /// </summary>
 545        /// <param name="value">The rune to convert to uppercase.</param>
 546        /// <returns>The specified rune converted to uppercase.</returns>
 547        public unsafe Rune ToUpper(Rune value)
 548        {
 549            // Convert rune to span
 0550            ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);
 551
 552            // Change span to upper and convert to rune
 0553            if (valueChars.Length == 2)
 554            {
 0555                Span<char> upperChars = ['\0', '\0'];
 0556                ToUpper(valueChars, upperChars);
 0557                return new Rune(upperChars[0], upperChars[1]);
 558            }
 559
 0560            char upperChar = ToUpper(valueChars[0]);
 0561            return new Rune(upperChar);
 562        }
 563
 564        private bool IsAsciiCasingSameAsInvariant
 565        {
 566            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 567            get
 568            {
 0569                if (_isAsciiCasingSameAsInvariant == NullableBool.Undefined)
 570                {
 0571                    PopulateIsAsciiCasingSameAsInvariant();
 572                }
 573
 0574                Debug.Assert(_isAsciiCasingSameAsInvariant == NullableBool.True || _isAsciiCasingSameAsInvariant == Null
 0575                return _isAsciiCasingSameAsInvariant == NullableBool.True;
 576            }
 577        }
 578
 579        [MethodImpl(MethodImplOptions.NoInlining)]
 580        private void PopulateIsAsciiCasingSameAsInvariant()
 581        {
 0582            bool compareResult = CultureInfo.GetCultureInfo(_textInfoName).CompareInfo.Compare("abcdefghijklmnopqrstuvwx
 0583            _isAsciiCasingSameAsInvariant = compareResult ? NullableBool.True : NullableBool.False;
 0584        }
 585
 586        /// <summary>
 587        /// Returns true if the dominant direction of text and UI such as the
 588        /// relative position of buttons and scroll bars
 589        /// </summary>
 0590        public bool IsRightToLeft => _cultureData.IsRightToLeft;
 591
 592        public override bool Equals([NotNullWhen(true)] object? obj)
 593        {
 0594            return obj is TextInfo otherTextInfo
 0595                && CultureName.Equals(otherTextInfo.CultureName);
 596        }
 597
 0598        public override int GetHashCode() => CultureName.GetHashCode();
 599
 600        public override string ToString()
 601        {
 0602            return "TextInfo - " + _cultureData.CultureName;
 603        }
 604
 605        /// <summary>
 606        /// Titlecasing refers to a casing practice wherein the first letter of a word is an uppercase letter
 607        /// and the rest of the letters are lowercase.  The choice of which words to titlecase in headings
 608        /// and titles is dependent on language and local conventions.  For example, "The Merry Wives of Windor"
 609        /// is the appropriate titlecasing of that play's name in English, with the word "of" not titlecased.
 610        /// In German, however, the title is "Die lustigen Weiber von Windsor," and both "lustigen" and "von"
 611        /// are not titlecased.  In French even fewer words are titlecased: "Les joyeuses commeres de Windsor."
 612        ///
 613        /// Moreover, the determination of what actually constitutes a word is language dependent, and this can
 614        /// influence which letter or letters of a "word" are uppercased when titlecasing strings.  For example
 615        /// "l'arbre" is considered two words in French, whereas "can't" is considered one word in English.
 616        /// </summary>
 617        public string ToTitleCase(string str)
 618        {
 0619            ArgumentNullException.ThrowIfNull(str);
 620
 0621            if (str.Length == 0)
 622            {
 0623                return str;
 624            }
 625
 0626            StringBuilder result = new StringBuilder();
 0627            string? lowercaseData = null;
 628            // Store if the current culture is Dutch (special case). This covers both the
 629            // neutral culture ("nl") and any specific Dutch culture ("nl-NL", "nl-BE", etc.).
 0630            string cultureName = CultureName;
 0631            bool isDutchCulture = cultureName.StartsWith("nl", StringComparison.OrdinalIgnoreCase) &&
 0632                (cultureName.Length == 2 || cultureName[2] == '-');
 633
 0634            for (int i = 0; i < str.Length; i++)
 635            {
 0636                UnicodeCategory charType = CharUnicodeInfo.GetUnicodeCategoryInternal(str, i, out int charLen);
 0637                if (char.CheckLetter(charType))
 638                {
 639                    // Special case to check for Dutch specific titlecasing with "IJ" characters
 640                    // at the beginning of a word
 0641                    if (isDutchCulture && i < str.Length - 1 && (str[i] == 'i' || str[i] == 'I') && (str[i + 1] == 'j' |
 642                    {
 0643                        result.Append("IJ");
 0644                        i += 2;
 645                    }
 646                    else
 647                    {
 648                        // Do the titlecasing for the first character of the word.
 0649                        i = AddTitlecaseLetter(ref result, ref str, i, charLen) + 1;
 650                    }
 651
 652                    // Convert the characters until the end of the this word
 653                    // to lowercase.
 0654                    int lowercaseStart = i;
 655
 656                    // Use hasLowerCase flag to prevent from lowercasing acronyms (like "URT", "USA", etc)
 657                    // This is in line with Word 2000 behavior of titlecasing.
 0658                    bool hasLowerCase = (charType == UnicodeCategory.LowercaseLetter);
 659
 660                    // Use a loop to find all of the other letters following this letter.
 0661                    while (i < str.Length)
 662                    {
 0663                        charType = CharUnicodeInfo.GetUnicodeCategoryInternal(str, i, out charLen);
 0664                        if (IsLetterCategory(charType))
 665                        {
 0666                            if (charType == UnicodeCategory.LowercaseLetter)
 667                            {
 0668                                hasLowerCase = true;
 669                            }
 0670                            i += charLen;
 671                        }
 0672                        else if (IsApostrophe(str[i]))
 673                        {
 0674                            i++;
 0675                            if (hasLowerCase)
 676                            {
 0677                                lowercaseData ??= ToLower(str);
 0678                                result.Append(lowercaseData, lowercaseStart, i - lowercaseStart);
 679                            }
 680                            else
 681                            {
 0682                                result.Append(str, lowercaseStart, i - lowercaseStart);
 683                            }
 0684                            lowercaseStart = i;
 0685                            hasLowerCase = true;
 686                        }
 0687                        else if (!IsWordSeparator(charType))
 688                        {
 689                            // This category is considered to be part of the word.
 690                            // This is any category that is marked as false in wordSeparator array.
 0691                            i += charLen;
 692                        }
 693                        else
 694                        {
 695                            // A word separator. Break out of the loop.
 696                            break;
 697                        }
 698                    }
 699
 0700                    int count = i - lowercaseStart;
 701
 0702                    if (count > 0)
 703                    {
 0704                        if (hasLowerCase)
 705                        {
 0706                            lowercaseData ??= ToLower(str);
 0707                            result.Append(lowercaseData, lowercaseStart, count);
 708                        }
 709                        else
 710                        {
 0711                            result.Append(str, lowercaseStart, count);
 712                        }
 713                    }
 714
 0715                    if (i < str.Length)
 716                    {
 717                        // not a letter, just append it
 0718                        i = AddNonLetter(ref result, ref str, i, charLen);
 719                    }
 720                }
 721                else
 722                {
 723                    // not a letter, just append it
 0724                    i = AddNonLetter(ref result, ref str, i, charLen);
 725                }
 726            }
 0727            return result.ToString();
 728        }
 729
 730        private static int AddNonLetter(ref StringBuilder result, ref string input, int inputIndex, int charLen)
 731        {
 0732            Debug.Assert(charLen == 1 || charLen == 2, "[TextInfo.AddNonLetter] CharUnicodeInfo.InternalGetUnicodeCatego
 0733            if (charLen == 2)
 734            {
 735                // Surrogate pair
 0736                result.Append(input[inputIndex++]);
 0737                result.Append(input[inputIndex]);
 738            }
 739            else
 740            {
 0741                result.Append(input[inputIndex]);
 742            }
 0743            return inputIndex;
 744        }
 745
 746        private int AddTitlecaseLetter(ref StringBuilder result, ref string input, int inputIndex, int charLen)
 747        {
 0748            Debug.Assert(charLen == 1 || charLen == 2, "[TextInfo.AddTitlecaseLetter] CharUnicodeInfo.InternalGetUnicode
 749
 0750            if (charLen == 2)
 751            {
 752                // for surrogate pairs do a ToUpper operation on the substring
 0753                ReadOnlySpan<char> src = input.AsSpan(inputIndex, 2);
 0754                if (GlobalizationMode.Invariant)
 755                {
 0756                    SurrogateCasing.ToUpper(src[0], src[1], out char h, out char l);
 0757                    result.Append(h);
 0758                    result.Append(l);
 759                }
 760                else
 761                {
 0762                    Span<char> dst = ['\0', '\0'];
 0763                    ChangeCaseToUpper(src, dst);
 0764                    result.Append(dst);
 765                }
 0766                inputIndex++;
 767            }
 768            else
 769            {
 0770                switch (input[inputIndex])
 771                {
 772                    // For AppCompat, the Titlecase Case Mapping data from NDP 2.0 is used below.
 773                    case (char)0x01C4:  // DZ with Caron -> Dz with Caron
 774                    case (char)0x01C5:  // Dz with Caron -> Dz with Caron
 775                    case (char)0x01C6:  // dz with Caron -> Dz with Caron
 0776                        result.Append((char)0x01C5);
 0777                        break;
 778                    case (char)0x01C7:  // LJ -> Lj
 779                    case (char)0x01C8:  // Lj -> Lj
 780                    case (char)0x01C9:  // lj -> Lj
 0781                        result.Append((char)0x01C8);
 0782                        break;
 783                    case (char)0x01CA:  // NJ -> Nj
 784                    case (char)0x01CB:  // Nj -> Nj
 785                    case (char)0x01CC:  // nj -> Nj
 0786                        result.Append((char)0x01CB);
 0787                        break;
 788                    case (char)0x01F1:  // DZ -> Dz
 789                    case (char)0x01F2:  // Dz -> Dz
 790                    case (char)0x01F3:  // dz -> Dz
 0791                        result.Append((char)0x01F2);
 0792                        break;
 793                    default:
 0794                        result.Append(GlobalizationMode.Invariant ? InvariantModeCasing.ToUpper(input[inputIndex]) : ToU
 795                        break;
 796                }
 797            }
 0798            return inputIndex;
 799        }
 800
 801        private unsafe void ChangeCaseCore(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper)
 802        {
 0803            if (GlobalizationMode.UseNls)
 804            {
 0805                NlsChangeCase(src, srcLen, dstBuffer, dstBufferCapacity, bToUpper);
 0806                return;
 807            }
 808#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 809            if (GlobalizationMode.Hybrid)
 810            {
 811                ChangeCaseNative(src, srcLen, dstBuffer, dstBufferCapacity, bToUpper);
 812                return;
 813            }
 814#endif
 0815            IcuChangeCase(src, srcLen, dstBuffer, dstBufferCapacity, bToUpper);
 0816        }
 817
 818        // Used in ToTitleCase():
 819        // When we find a starting letter, the following array decides if a category should be
 820        // considered as word separator or not.
 821        private const int c_wordSeparatorMask =
 822            /* false */ (0 <<  0) | // UppercaseLetter = 0,
 823            /* false */ (0 <<  1) | // LowercaseLetter = 1,
 824            /* false */ (0 <<  2) | // TitlecaseLetter = 2,
 825            /* false */ (0 <<  3) | // ModifierLetter = 3,
 826            /* false */ (0 <<  4) | // OtherLetter = 4,
 827            /* false */ (0 <<  5) | // NonSpacingMark = 5,
 828            /* false */ (0 <<  6) | // SpacingCombiningMark = 6,
 829            /* false */ (0 <<  7) | // EnclosingMark = 7,
 830            /* false */ (0 <<  8) | // DecimalDigitNumber = 8,
 831            /* false */ (0 <<  9) | // LetterNumber = 9,
 832            /* false */ (0 << 10) | // OtherNumber = 10,
 833            /* true  */ (1 << 11) | // SpaceSeparator = 11,
 834            /* true  */ (1 << 12) | // LineSeparator = 12,
 835            /* true  */ (1 << 13) | // ParagraphSeparator = 13,
 836            /* true  */ (1 << 14) | // Control = 14,
 837            /* true  */ (1 << 15) | // Format = 15,
 838            /* false */ (0 << 16) | // Surrogate = 16,
 839            /* false */ (0 << 17) | // PrivateUse = 17,
 840            /* true  */ (1 << 18) | // ConnectorPunctuation = 18,
 841            /* true  */ (1 << 19) | // DashPunctuation = 19,
 842            /* true  */ (1 << 20) | // OpenPunctuation = 20,
 843            /* true  */ (1 << 21) | // ClosePunctuation = 21,
 844            /* true  */ (1 << 22) | // InitialQuotePunctuation = 22,
 845            /* true  */ (1 << 23) | // FinalQuotePunctuation = 23,
 846            /* true  */ (1 << 24) | // OtherPunctuation = 24,
 847            /* true  */ (1 << 25) | // MathSymbol = 25,
 848            /* true  */ (1 << 26) | // CurrencySymbol = 26,
 849            /* true  */ (1 << 27) | // ModifierSymbol = 27,
 850            /* true  */ (1 << 28) | // OtherSymbol = 28,
 851            /* false */ (0 << 29);  // OtherNotAssigned = 29;
 852
 853        private static bool IsWordSeparator(UnicodeCategory category)
 854        {
 0855            return (c_wordSeparatorMask & (1 << (int)category)) != 0;
 856        }
 857
 858        // Characters treated as an apostrophe within a word (e.g. contractions such as
 859        // "can't" or possessives such as "Grandma's"), so a following letter is not treated
 860        // as the start of a new word during titlecasing:
 861        //   U+0027 APOSTROPHE
 862        //   U+2019 RIGHT SINGLE QUOTATION MARK (the typographic curly apostrophe)
 863        //   U+2018 LEFT SINGLE QUOTATION MARK
 864        //   U+FF07 FULLWIDTH APOSTROPHE
 865        private static bool IsApostrophe(char c)
 866        {
 0867            return c is '\'' or '\u2019' or '\u2018' or '\uFF07';
 868        }
 869
 870        private static bool IsLetterCategory(UnicodeCategory uc)
 871        {
 0872            return uc == UnicodeCategory.UppercaseLetter
 0873                 || uc == UnicodeCategory.LowercaseLetter
 0874                 || uc == UnicodeCategory.TitlecaseLetter
 0875                 || uc == UnicodeCategory.ModifierLetter
 0876                 || uc == UnicodeCategory.OtherLetter;
 877        }
 878
 879        // A dummy struct that is used for 'ToUpper' in generic parameters
 880        private readonly struct ToUpperConversion { }
 881
 882        // A dummy struct that is used for 'ToLower' in generic parameters
 883        private readonly struct ToLowerConversion { }
 884    }
 885}
 886

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.Icu.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Diagnostics.CodeAnalysis;
 6
 7namespace System.Globalization
 8{
 9    public partial class TextInfo
 10    {
 11        private NullableBool _needsTurkishCasing;
 12
 13        private static bool NeedsTurkishCasing(string localeName)
 14        {
 015            Debug.Assert(localeName != null);
 16
 017            return CultureInfo.GetCultureInfo(localeName).CompareInfo.Compare("\u0131", "I", CompareOptions.IgnoreCase) 
 18        }
 19
 20        internal unsafe void IcuChangeCase(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper)
 21        {
 022            Debug.Assert(!GlobalizationMode.Invariant);
 023            Debug.Assert(!GlobalizationMode.UseNls);
 24
 025            if (HasEmptyCultureName)
 26            {
 027                Interop.Globalization.ChangeCaseInvariant(src, srcLen, dstBuffer, dstBufferCapacity, bToUpper);
 28            }
 29            else
 30            {
 031                if (_needsTurkishCasing == NullableBool.Undefined)
 32                {
 033                    _needsTurkishCasing = NeedsTurkishCasing(_textInfoName) ? NullableBool.True : NullableBool.False;
 34                }
 035                if (_needsTurkishCasing == NullableBool.True)
 36                {
 037                    Interop.Globalization.ChangeCaseTurkish(src, srcLen, dstBuffer, dstBufferCapacity, bToUpper);
 38                }
 39                else
 40                {
 041                    Interop.Globalization.ChangeCase(src, srcLen, dstBuffer, dstBufferCapacity, bToUpper);
 42                }
 43            }
 044        }
 45
 46    }
 47}
 48

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.Nls.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Diagnostics.CodeAnalysis;
 6
 7namespace System.Globalization
 8{
 9    public partial class TextInfo
 10    {
 11        private unsafe void NlsChangeCase(char* pSource, int pSourceLen, char* pResult, int pResultLen, bool toUpper)
 12        {
 013            Debug.Assert(!GlobalizationMode.Invariant);
 014            Debug.Assert(GlobalizationMode.UseNls);
 015            Debug.Assert(pSource != null);
 016            Debug.Assert(pResult != null);
 017            Debug.Assert(pSourceLen >= 0);
 018            Debug.Assert(pResultLen >= 0);
 019            Debug.Assert(pSourceLen <= pResultLen);
 20
 21            // Check for Invariant to avoid A/V in LCMapStringEx
 22            // We don't specify LCMAP_LINGUISTIC_CASING for Invariant because it will enable Turkish-I behavior too whic
 23            // right for Invariant.
 024            uint linguisticCasing = IsInvariantLocale(_textInfoName) ? 0 : LCMAP_LINGUISTIC_CASING;
 25
 026            int ret = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName,
 027                                                     linguisticCasing | (toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE),
 028                                                     pSource,
 029                                                     pSourceLen,
 030                                                     pResult,
 031                                                     pSourceLen,
 032                                                     null,
 033                                                     null,
 034                                                     _sortHandle);
 035            if (ret == 0)
 36            {
 037                throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 38            }
 39
 040            Debug.Assert(ret == pSourceLen, "Expected getting the same length of the original string");
 041        }
 42
 43        // PAL Ends here
 44
 45        private IntPtr _sortHandle;
 46
 47        private const uint LCMAP_LINGUISTIC_CASING = 0x01000000;
 48        private const uint LCMAP_LOWERCASE = 0x00000100;
 49        private const uint LCMAP_UPPERCASE = 0x00000200;
 50
 051        private static bool IsInvariantLocale(string localeName) => localeName == "";
 52    }
 53}
 54

Methods/Properties

HasEmptyCultureName()
.cctor()
.ctor(System.Globalization.CultureData)
.ctor(System.Globalization.CultureData,System.Boolean)
System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(System.Object)
ANSICodePage()
OEMCodePage()
MacCodePage()
EBCDICCodePage()
LCID()
CultureName()
IsReadOnly()
Clone()
ReadOnly(System.Globalization.TextInfo)
VerifyWritable()
SetReadOnlyState(System.Boolean)
ListSeparator()
ListSeparator(System.String)
ToLower(System.Char)
ToLowerInvariant(System.Char)
ToLower(System.String)
ToLowerInvariant(System.String)
ToLower(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Char>)
ChangeCase(System.Char,System.Boolean)
ToUpperOrdinal(System.Char)
ToLowerOrdinal(System.Char)
PreserveOrdinalLowerCasingClass(System.Char,System.Char)
ChangeCaseToLower(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Char>)
ChangeCaseToUpper(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Char>)
ChangeCaseCommon(System.Globalization.TextInfo,System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Char>)
ChangeCaseCommon(System.Globalization.TextInfo,System.String)
ToLowerAsciiInvariant(System.String)
ToLowerAsciiInvariant(System.Char)
ToUpper(System.Char)
ToUpperInvariant(System.Char)
ToUpper(System.String)
ToUpperInvariant(System.String)
ToUpper(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Char>)
ToUpperAsciiInvariant(System.Char)
ToLower(System.Text.Rune)
ToUpper(System.Text.Rune)
IsAsciiCasingSameAsInvariant()
PopulateIsAsciiCasingSameAsInvariant()
IsRightToLeft()
Equals(System.Object)
GetHashCode()
ToString()
ToTitleCase(System.String)
AddNonLetter(System.Text.StringBuilder&,System.String&,System.Int32,System.Int32)
AddTitlecaseLetter(System.Text.StringBuilder&,System.String&,System.Int32,System.Int32)
ChangeCaseCore(System.Char*,System.Int32,System.Char*,System.Int32,System.Boolean)
IsWordSeparator(System.Globalization.UnicodeCategory)
IsApostrophe(System.Char)
IsLetterCategory(System.Globalization.UnicodeCategory)
NeedsTurkishCasing(System.String)
IcuChangeCase(System.Char*,System.Int32,System.Char*,System.Int32,System.Boolean)
NlsChangeCase(System.Char*,System.Int32,System.Char*,System.Int32,System.Boolean)
IsInvariantLocale(System.String)