// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== //////////////////////////////////////////////////////////////////////////// // // Class: TextInfo // // Purpose: This Class defines behaviors specific to a writing system. // A writing system is the collection of scripts and // orthographic rules required to represent a language as text. // // Date: [....] 31, 1999 // //////////////////////////////////////////////////////////////////////////// using System.Security; namespace System.Globalization { using System; using System.Text; using System.Threading; using System.Runtime; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security.Permissions; using System.Diagnostics.Contracts; [Serializable] [System.Runtime.InteropServices.ComVisible(true)] public class TextInfo : ICloneable, IDeserializationCallback { //--------------------------------------------------------------------// // Internal Information // //--------------------------------------------------------------------// // // Variables. // [OptionalField(VersionAdded = 2)] private String m_listSeparator; [OptionalField(VersionAdded = 2)] private bool m_isReadOnly = false; // // In Whidbey we had several names: // m_win32LangID is the name of the culture, but only used for (de)serialization. // customCultureName is the name of the creating custom culture (if custom) In combination with m_win32LangID // this is authoratative, ie when deserializing. // m_cultureTableRecord was the data record of the creating culture. (could have different name if custom) // m_textInfoID is the LCID of the textinfo itself (no longer used) // m_name is the culture name (from cultureinfo.name) // // In Silverlight/Arrowhead this is slightly different: // m_cultureName is the name of the creating culture. Note that we consider this authoratative, // if the culture's textinfo changes when deserializing, then behavior may change. // (ala Whidbey behavior). This is the only string Arrowhead needs to serialize. // m_cultureData is the data that backs this class. // m_textInfoName is the actual name of the textInfo (from cultureData.STEXTINFO) // m_textInfoName can be the same as m_cultureName on Silverlight since the OS knows // how to do the sorting. However in the desktop, when we call the sorting dll, it doesn't // know how to resolve custom locle names to sort ids so we have to have alredy resolved this. // [OptionalField(VersionAdded = 3)] private String m_cultureName; // Name of the culture that created this text info [NonSerialized]private CultureData m_cultureData; // Data record for the culture that made us, not for this textinfo [NonSerialized]private String m_textInfoName; // Name of the text info we're using (ie: m_cultureData.STEXTINFO) [NonSerialized]private IntPtr m_dataHandle; // Sort handle [NonSerialized]private IntPtr m_handleOrigin; [NonSerialized]private bool? m_IsAsciiCasingSameAsInvariant; // Invariant text info internal static TextInfo Invariant { get { if (s_Invariant == null) s_Invariant = new TextInfo(CultureData.Invariant); return s_Invariant; } } internal volatile static TextInfo s_Invariant; //////////////////////////////////////////////////////////////////////// // // TextInfo Constructors // // Implements CultureInfo.TextInfo. // //////////////////////////////////////////////////////////////////////// internal TextInfo(CultureData cultureData) { // This is our primary data source, we don't need most of the rest of this this.m_cultureData = cultureData; this.m_cultureName = this.m_cultureData.CultureName; this.m_textInfoName = this.m_cultureData.STEXTINFO; #if !FEATURE_CORECLR IntPtr handleOrigin; this.m_dataHandle = CompareInfo.InternalInitSortHandle(m_textInfoName, out handleOrigin); this.m_handleOrigin = handleOrigin; #endif } //////////////////////////////////////////////////////////////////////// // // Serialization / Deserialization // // Note that we have to respect the Whidbey behavior for serialization compatibility // //////////////////////////////////////////////////////////////////////// #region Serialization // the following fields are defined to keep the compatibility with Whidbey. // don't change/remove the names/types of these fields. [OptionalField(VersionAdded = 2)] private string customCultureName; #if !FEATURE_CORECLR // the following fields are defined to keep compatibility with Everett. // don't change/remove the names/types of these fields. [OptionalField(VersionAdded = 1)] internal int m_nDataItem; [OptionalField(VersionAdded = 1)] internal bool m_useUserOverride; [OptionalField(VersionAdded = 1)] internal int m_win32LangID; #endif // !FEATURE_CORECLR [OnDeserializing] private void OnDeserializing(StreamingContext ctx) { // Clear these so we can check if we've fixed them yet this.m_cultureData = null; this.m_cultureName = null; } private void OnDeserialized() { // this method will be called twice because of the support of IDeserializationCallback if (this.m_cultureData == null) { if (this.m_cultureName == null) { // This is whidbey data, get it from customCultureName/win32langid if (this.customCultureName != null) { // They gave a custom cultuer name, so use that this.m_cultureName = this.customCultureName; } #if FEATURE_USE_LCID else { if (m_win32LangID == 0) { // m_cultureName and m_win32LangID are nulls which means we got uninitialized textinfo serialization stream. // To be compatible with v2/3/3.5 we need to return ar-SA TextInfo in this case. m_cultureName = "ar-SA"; } else { // No custom culture, use the name from the LCID m_cultureName = CultureInfo.GetCultureInfo(m_win32LangID).m_cultureData.CultureName; } } #endif } // Get the text info name belonging to that culture this.m_cultureData = CultureInfo.GetCultureInfo(m_cultureName).m_cultureData; this.m_textInfoName = this.m_cultureData.STEXTINFO; #if !FEATURE_CORECLR IntPtr handleOrigin; this.m_dataHandle = CompareInfo.InternalInitSortHandle(m_textInfoName, out handleOrigin); this.m_handleOrigin = handleOrigin; #endif } } [OnDeserialized] private void OnDeserialized(StreamingContext ctx) { OnDeserialized(); } [OnSerializing] private void OnSerializing(StreamingContext ctx) { #if !FEATURE_CORECLR // Initialize the fields Whidbey expects: // Whidbey expected this, so set it, but the value doesn't matter much this.m_useUserOverride = false; #endif // FEATURE_CORECLR // Relabel our name since Whidbey expects it to be called customCultureName this.customCultureName = this.m_cultureName; #if FEATURE_USE_LCID // Ignore the m_win32LangId because whidbey'll just get it by name if we make it the LOCALE_CUSTOM_UNSPECIFIED. this.m_win32LangID = (CultureInfo.GetCultureInfo(m_cultureName)).LCID; #endif } #endregion Serialization // // Internal ordinal comparison functions // [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] internal static int GetHashCodeOrdinalIgnoreCase(String s) { return GetHashCodeOrdinalIgnoreCase(s, false, 0); } [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] internal static int GetHashCodeOrdinalIgnoreCase(String s, bool forceRandomizedHashing, long additionalEntropy) { // This is the same as an case insensitive hash for Invariant // (not necessarily true for sorting, but OK for casing & then we apply normal hash code rules) return (Invariant.GetCaseInsensitiveHashCode(s, forceRandomizedHashing, additionalEntropy)); } [System.Security.SecuritySafeCritical] [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] internal static unsafe bool TryFastFindStringOrdinalIgnoreCase(int searchFlags, String source, int startIndex, String value, int count, ref int foundIndex) { return InternalTryFindStringOrdinalIgnoreCase(searchFlags, source, count, startIndex, value, value.Length, ref foundIndex); } // This function doesn't check arguments. Please do check in the caller. // The underlying unmanaged code will assert the sanity of arguments. [System.Security.SecuritySafeCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] internal static unsafe int CompareOrdinalIgnoreCase(String str1, String str2) { #if __APPLE__ // ToUpper (invariant) here before going to the PAL since Mac OS 10.4 does this step // wrong (CFCompareString uses the current OS locale and does not let you specify a specific locale) return String.CompareOrdinal(str1.ToUpper(CultureInfo.InvariantCulture), str2.ToUpper(CultureInfo.InvariantCulture)); #else // Compare the whole string and ignore case. return InternalCompareStringOrdinalIgnoreCase(str1, 0, str2, 0, str1.Length, str2.Length); #endif } // This function doesn't check arguments. Please do check in the caller. // The underlying unmanaged code will assert the sanity of arguments. [System.Security.SecuritySafeCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] internal static unsafe int CompareOrdinalIgnoreCaseEx(String strA, int indexA, String strB, int indexB, int lengthA, int lengthB ) { Contract.Assert(strA.Length >= indexA + lengthA, "[TextInfo.CompareOrdinalIgnoreCaseEx] Caller should've validated strA.Length >= indexA + lengthA"); Contract.Assert(strB.Length >= indexB + lengthB, "[TextInfo.CompareOrdinalIgnoreCaseEx] Caller should've validated strB.Length >= indexB + lengthB"); #if __APPLE__ // ToUpper (invariant) here before going to the PAL since Mac OS 10.4 does this step // wrong (CFCompareString uses the current OS locale and does not let you specify a specific locale) return String.CompareOrdinal(strA.ToUpper(CultureInfo.InvariantCulture), indexA, strB.ToUpper(CultureInfo.InvariantCulture), indexB, Math.Max(lengthA, lengthB)); #else return InternalCompareStringOrdinalIgnoreCase(strA, indexA, strB, indexB, lengthA, lengthB); #endif } [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] internal static int IndexOfStringOrdinalIgnoreCase(String source, String value, int startIndex, int count) { Contract.Assert(source != null, "[TextInfo.IndexOfStringOrdinalIgnoreCase] Caller should've validated source != null"); Contract.Assert(value != null, "[TextInfo.IndexOfStringOrdinalIgnoreCase] Caller should've validated value != null"); Contract.Assert(startIndex + count <= source.Length, "[TextInfo.IndexOfStringOrdinalIgnoreCase] Caller should've validated startIndex + count <= source.Length"); // We return 0 if both inputs are empty strings if (source.Length == 0 && value.Length == 0) { return 0; } #if __APPLE__ string sourceUpper = source.ToUpper(CultureInfo.InvariantCulture); string valueUpper = value.ToUpper(CultureInfo.InvariantCulture); #else // fast path int ret = -1; if (TryFastFindStringOrdinalIgnoreCase(Microsoft.Win32.Win32Native.FIND_FROMSTART, source, startIndex, value, count, ref ret)) return ret; #endif // the search space within [source] starts at offset [startIndex] inclusive and includes // [count] characters (thus the last included character is at index [startIndex + count -1] // [end] is the index of the next character after the search space // (it points past the end of the search space) int end = startIndex + count; // maxStartIndex is the index beyond which we never *start* searching, inclusive; in other words; // a search could include characters beyond maxStartIndex, but we'd never begin a search at an // index strictly greater than maxStartIndex. int maxStartIndex = end - value.Length; for (; startIndex <= maxStartIndex; startIndex++) { #if __APPLE__ if (String.CompareOrdinal(sourceUpper, startIndex, valueUpper, 0, value.Length)==0) #else // We should always have the same or more characters left to search than our actual pattern Contract.Assert(end - startIndex >= value.Length); // since this is an ordinal comparison, we can assume that the lengths must match if (CompareOrdinalIgnoreCaseEx(source, startIndex, value, 0, value.Length, value.Length) == 0) #endif { return startIndex; } } // Not found return -1; } #if FEATURE_CORECLR private static bool LegacyMode { get { return CompatibilitySwitches.IsAppEarlierThanSilverlight4; } } #endif // FEATURE_CORECLR [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] internal static int LastIndexOfStringOrdinalIgnoreCase(String source, String value, int startIndex, int count) { Contract.Assert(source != null, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated source != null"); Contract.Assert(value != null, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated value != null"); Contract.Assert(startIndex - count+1 >= 0, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated startIndex - count+1 >= 0"); Contract.Assert(startIndex <= source.Length, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated startIndex <= source.Length"); // If value is Empty, the return value is startIndex // we accidently shipped Silverlight 2 and 3 without this if-check if (value.Length == 0 #if FEATURE_CORECLR && !LegacyMode #endif // FEATURE_CORECLR ) { return startIndex; } #if __APPLE__ string sourceUpper = source.ToUpper(CultureInfo.InvariantCulture); string valueUpper = value.ToUpper(CultureInfo.InvariantCulture); #else // fast path int ret = -1; if (TryFastFindStringOrdinalIgnoreCase(Microsoft.Win32.Win32Native.FIND_FROMEND, source, startIndex, value, count, ref ret)) return ret; #endif // the search space within [source] ends at offset [startIndex] inclusive // and includes [count] characters // minIndex is the first included character and is at index [startIndex - count + 1] int minIndex = startIndex - count + 1; // First place we can find it is start index - (value.length -1) if (value.Length > 0) { startIndex -= (value.Length - 1); } for (; startIndex >= minIndex; startIndex--) { #if __APPLE__ if (String.CompareOrdinal(sourceUpper, startIndex, valueUpper, 0, value.Length)==0) #else if (CompareOrdinalIgnoreCaseEx(source, startIndex, value, 0, value.Length, value.Length) == 0) #endif { return startIndex; } } // Not found return -1; } //////////////////////////////////////////////////////////////////////// // // CodePage // // Returns the number of the code page used by this writing system. // The type parameter can be any of the following values: // ANSICodePage // OEMCodePage // MACCodePage // //////////////////////////////////////////////////////////////////////// #if !FEATURE_CORECLR public virtual int ANSICodePage { get { return (this.m_cultureData.IDEFAULTANSICODEPAGE); } } public virtual int OEMCodePage { get { return (this.m_cultureData.IDEFAULTOEMCODEPAGE); } } public virtual int MacCodePage { get { return (this.m_cultureData.IDEFAULTMACCODEPAGE); } } public virtual int EBCDICCodePage { get { return (this.m_cultureData.IDEFAULTEBCDICCODEPAGE); } } #endif //////////////////////////////////////////////////////////////////////// // // LCID // // We need a way to get an LCID from outside of the BCL. This prop is the way. // NOTE: neutral cultures will cause GPS incorrect LCIDS from this // //////////////////////////////////////////////////////////////////////// #if FEATURE_USE_LCID [System.Runtime.InteropServices.ComVisible(false)] public int LCID { get { // Just use the LCID from our text info name return CultureInfo.GetCultureInfo(this.m_textInfoName).LCID; } } #endif //////////////////////////////////////////////////////////////////////// // // CultureName // // The name of the culture associated with the current TextInfo. // //////////////////////////////////////////////////////////////////////// [System.Runtime.InteropServices.ComVisible(false)] public string CultureName { get { return(this.m_textInfoName); } } //////////////////////////////////////////////////////////////////////// // // IsReadOnly // // Detect if the object is readonly. // //////////////////////////////////////////////////////////////////////// [System.Runtime.InteropServices.ComVisible(false)] public bool IsReadOnly { get { return (m_isReadOnly); } } //////////////////////////////////////////////////////////////////////// // // Clone // // Is the implementation of IColnable. // //////////////////////////////////////////////////////////////////////// [System.Runtime.InteropServices.ComVisible(false)] public virtual Object Clone() { object o = MemberwiseClone(); ((TextInfo) o).SetReadOnlyState(false); return (o); } //////////////////////////////////////////////////////////////////////// // // ReadOnly // // Create a cloned readonly instance or return the input one if it is // readonly. // //////////////////////////////////////////////////////////////////////// [System.Runtime.InteropServices.ComVisible(false)] public static TextInfo ReadOnly(TextInfo textInfo) { if (textInfo == null) { throw new ArgumentNullException("textInfo"); } Contract.EndContractBlock(); if (textInfo.IsReadOnly) { return (textInfo); } TextInfo clonedTextInfo = (TextInfo)(textInfo.MemberwiseClone()); clonedTextInfo.SetReadOnlyState(true); return (clonedTextInfo); } private void VerifyWritable() { if (m_isReadOnly) { throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly")); } Contract.EndContractBlock(); } internal void SetReadOnlyState(bool readOnly) { m_isReadOnly = readOnly; } //////////////////////////////////////////////////////////////////////// // // ListSeparator // // Returns the string used to separate items in a list. // //////////////////////////////////////////////////////////////////////// public virtual String ListSeparator { [System.Security.SecuritySafeCritical] // auto-generated get { if (m_listSeparator == null) { m_listSeparator = this.m_cultureData.SLIST; } return (m_listSeparator); } [System.Runtime.InteropServices.ComVisible(false)] set { if (value == null) { throw new ArgumentNullException("value", Environment.GetResourceString("ArgumentNull_String")); } Contract.EndContractBlock(); VerifyWritable(); m_listSeparator = value; } } //////////////////////////////////////////////////////////////////////// // // ToLower // // Converts the character or string to lower case. Certain locales // have different casing semantics from the file systems in Win32. // //////////////////////////////////////////////////////////////////////// [System.Security.SecuritySafeCritical] // auto-generated #if !FEATURE_CORECLR [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] #endif public unsafe virtual char ToLower(char c) { if(IsAscii(c) && IsAsciiCasingSameAsInvariant) { return ToLowerAsciiInvariant(c); } return (InternalChangeCaseChar(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, c, false)); } [System.Security.SecuritySafeCritical] // auto-generated public unsafe virtual String ToLower(String str) { if (str == null) { throw new ArgumentNullException("str"); } Contract.EndContractBlock(); String toLower = InternalChangeCaseString(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, str, false); #if __APPLE__ // // Mac OS X has a documented list of "illegal" (precomposed) characters // http://developer.apple.com/technotes/tn/tn1150table.html // // These characters are mostly in the EXTENDED_GREEK range. Apple decomposes // these characters into a sequence of two or more characters that is a // canonical or compatibility equivalent. // // In the extremely unlikely event that an illegal character is in the String, // fallback to using slower Char routines since they do not decompose // the illegal characters. // if (toLower.Length != str.Length) { char[] chars = new char[str.Length]; for (int i = 0; i < str.Length; i++) { chars[i] = this.ToLower(str[i]); } toLower = new String(chars); } #endif return toLower; } static private Char ToLowerAsciiInvariant(Char c) { if ('A' <= c && c <= 'Z') { c = (Char)(c | 0x20); } return c; } //////////////////////////////////////////////////////////////////////// // // ToUpper // // Converts the character or string to upper case. Certain locales // have different casing semantics from the file systems in Win32. // //////////////////////////////////////////////////////////////////////// [System.Security.SecuritySafeCritical] // auto-generated #if !FEATURE_CORECLR [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] #endif public unsafe virtual char ToUpper(char c) { if (IsAscii(c) && IsAsciiCasingSameAsInvariant) { return ToUpperAsciiInvariant(c); } return (InternalChangeCaseChar(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, c, true)); } [System.Security.SecuritySafeCritical] // auto-generated public unsafe virtual String ToUpper(String str) { if (str == null) { throw new ArgumentNullException("str"); } Contract.EndContractBlock(); String toUpper = InternalChangeCaseString(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, str, true); #if __APPLE__ // // Mac OS X has a documented list of "illegal" (precomposed) characters // http://developer.apple.com/technotes/tn/tn1150table.html // // These characters are mostly in the EXTENDED_GREEK range. Apple decomposes // these characters into a sequence of two or more characters that is a // canonical or compatibility equivalent. // // In the extremely unlikely event that an illegal character is in the String, // fallback to using slower Char routines since they do not decompose // the illegal characters. // if (toUpper.Length != str.Length) { char[] chars = new char[str.Length]; for (int i = 0; i < str.Length; i++) { chars[i] = this.ToUpper(str[i]); } toUpper = new String(chars); } #endif return toUpper; } static private Char ToUpperAsciiInvariant(Char c) { if ('a' <= c && c <= 'z') { c = (Char)(c & ~0x20); } return c; } static private bool IsAscii(Char c) { return c < 0x80; } private bool IsAsciiCasingSameAsInvariant { get { if (m_IsAsciiCasingSameAsInvariant == null) { m_IsAsciiCasingSameAsInvariant = CultureInfo.GetCultureInfo(m_textInfoName).CompareInfo.Compare("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", CompareOptions.IgnoreCase) == 0; } return (bool)m_IsAsciiCasingSameAsInvariant; } } //////////////////////////////////////////////////////////////////////// // // Equals // // Implements Object.Equals(). Returns a boolean indicating whether // or not object refers to the same CultureInfo as the current instance. // //////////////////////////////////////////////////////////////////////// public override bool Equals(Object obj) { TextInfo that = obj as TextInfo; if (that != null) { return this.CultureName.Equals(that.CultureName); } return (false); } //////////////////////////////////////////////////////////////////////// // // GetHashCode // // Implements Object.GetHashCode(). Returns the hash code for the // CultureInfo. The hash code is guaranteed to be the same for CultureInfo A // and B where A.Equals(B) is true. // //////////////////////////////////////////////////////////////////////// public override int GetHashCode() { return (this.CultureName.GetHashCode()); } //////////////////////////////////////////////////////////////////////// // // ToString // // Implements Object.ToString(). Returns a string describing the // TextInfo. // //////////////////////////////////////////////////////////////////////// public override String ToString() { return ("TextInfo - " + this.m_cultureData.CultureName); } // // Titlecasing: // ----------- // Titlecasing refers to a casing practice wherein the first letter of a word is an uppercase letter // and the rest of the letters are lowercase. The choice of which words to titlecase in headings // and titles is dependent on language and local conventions. For example, "The Merry Wives of Windor" // is the appropriate titlecasing of that play's name in English, with the word "of" not titlecased. // In German, however, the title is "Die lustigen Weiber von Windsor," and both "lustigen" and "von" // are not titlecased. In French even fewer words are titlecased: "Les joyeuses commeres de Windsor." // // Moreover, the determination of what actually constitutes a word is language dependent, and this can // influence which letter or letters of a "word" are uppercased when titlecasing strings. For example // "l'arbre" is considered two words in French, whereas "can't" is considered one word in English. // // // Differences between UNICODE 5.0 and the .NET Framework ( #if !FEATURE_CORECLR public unsafe String ToTitleCase(String str) { if (str==null) { throw new ArgumentNullException("str"); } Contract.EndContractBlock(); if (str.Length == 0) { return (str); } StringBuilder result = new StringBuilder(); String lowercaseData = null; for (int i = 0; i < str.Length; i++) { UnicodeCategory charType; int charLen; charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen); if (Char.CheckLetter(charType)) { // Do the titlecasing for the first character of the word. i = AddTitlecaseLetter(ref result, ref str, i, charLen) + 1; // // Convert the characters until the end of the this word // to lowercase. // int lowercaseStart = i; // // Use hasLowerCase flag to prevent from lowercasing acronyms (like "URT", "USA", etc) // This is in line with Word 2000 behavior of titlecasing. // bool hasLowerCase = (charType == UnicodeCategory.LowercaseLetter); // Use a loop to find all of the other letters following this letter. while (i < str.Length) { charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen); if (IsLetterCategory(charType)) { if (charType == UnicodeCategory.LowercaseLetter) { hasLowerCase = true; } i += charLen; } else if (str[i] == '\'') { // i++; if (hasLowerCase) { if (lowercaseData==null) { lowercaseData = this.ToLower(str); } result.Append(lowercaseData, lowercaseStart, i - lowercaseStart); } else { result.Append(str, lowercaseStart, i - lowercaseStart); } lowercaseStart = i; hasLowerCase = true; } else if (!IsWordSeparator(charType)) { // This category is considered to be part of the word. // This is any category that is marked as false in wordSeprator array. i+= charLen; } else { // A word separator. Break out of the loop. break; } } int count = i - lowercaseStart; if (count>0) { if (hasLowerCase) { if (lowercaseData==null) { lowercaseData = this.ToLower(str); } result.Append(lowercaseData, lowercaseStart, count); } else { result.Append(str, lowercaseStart, count); } } if (i < str.Length) { // not a letter, just append it i = AddNonLetter(ref result, ref str, i, charLen); } } else { // not a letter, just append it i = AddNonLetter(ref result, ref str, i, charLen); } } return (result.ToString()); } private static int AddNonLetter(ref StringBuilder result, ref String input, int inputIndex, int charLen) { Contract.Assert(charLen == 1 || charLen == 2, "[TextInfo.AddNonLetter] CharUnicodeInfo.InternalGetUnicodeCategory returned an unexpected charLen!"); if (charLen == 2) { // Surrogate pair result.Append(input[inputIndex++]); result.Append(input[inputIndex]); } else { result.Append(input[inputIndex]); } return inputIndex; } private int AddTitlecaseLetter(ref StringBuilder result, ref String input, int inputIndex, int charLen) { Contract.Assert(charLen == 1 || charLen == 2, "[TextInfo.AddTitlecaseLetter] CharUnicodeInfo.InternalGetUnicodeCategory returned an unexpected charLen!"); // for surrogate pairs do a simple ToUpper operation on the substring if (charLen == 2) { // Surrogate pair result.Append( this.ToUpper(input.Substring(inputIndex, charLen)) ); inputIndex++; } else { switch (input[inputIndex]) { // // For AppCompat, the Titlecase Case Mapping data from NDP 2.0 is used below. // case (char)0x01C4: // DZ with Caron -> Dz with Caron case (char)0x01C5: // Dz with Caron -> Dz with Caron case (char)0x01C6: // dz with Caron -> Dz with Caron result.Append( (char)0x01C5 ); break; case (char)0x01C7: // LJ -> Lj case (char)0x01C8: // Lj -> Lj case (char)0x01C9: // lj -> Lj result.Append( (char)0x01C8 ); break; case (char)0x01CA: // NJ -> Nj case (char)0x01CB: // Nj -> Nj case (char)0x01CC: // nj -> Nj result.Append( (char)0x01CB ); break; case (char)0x01F1: // DZ -> Dz case (char)0x01F2: // Dz -> Dz case (char)0x01F3: // dz -> Dz result.Append( (char)0x01F2 ); break; default: result.Append( this.ToUpper(input[inputIndex]) ); break; } } return inputIndex; } // // Used in ToTitleCase(): // When we find a starting letter, the following array decides if a category should be // considered as word seprator or not. // private const int wordSeparatorMask = /* false */ (0 << 0) | // UppercaseLetter = 0, /* false */ (0 << 1) | // LowercaseLetter = 1, /* false */ (0 << 2) | // TitlecaseLetter = 2, /* false */ (0 << 3) | // ModifierLetter = 3, /* false */ (0 << 4) | // OtherLetter = 4, /* false */ (0 << 5) | // NonSpacingMark = 5, /* false */ (0 << 6) | // SpacingCombiningMark = 6, /* false */ (0 << 7) | // EnclosingMark = 7, /* false */ (0 << 8) | // DecimalDigitNumber = 8, /* false */ (0 << 9) | // LetterNumber = 9, /* false */ (0 << 10) | // OtherNumber = 10, /* true */ (1 << 11) | // SpaceSeparator = 11, /* true */ (1 << 12) | // LineSeparator = 12, /* true */ (1 << 13) | // ParagraphSeparator = 13, /* true */ (1 << 14) | // Control = 14, /* true */ (1 << 15) | // Format = 15, /* false */ (0 << 16) | // Surrogate = 16, /* false */ (0 << 17) | // PrivateUse = 17, /* true */ (1 << 18) | // ConnectorPunctuation = 18, /* true */ (1 << 19) | // DashPunctuation = 19, /* true */ (1 << 20) | // OpenPunctuation = 20, /* true */ (1 << 21) | // ClosePunctuation = 21, /* true */ (1 << 22) | // InitialQuotePunctuation = 22, /* true */ (1 << 23) | // FinalQuotePunctuation = 23, /* true */ (1 << 24) | // OtherPunctuation = 24, /* true */ (1 << 25) | // MathSymbol = 25, /* true */ (1 << 26) | // CurrencySymbol = 26, /* true */ (1 << 27) | // ModifierSymbol = 27, /* true */ (1 << 28) | // OtherSymbol = 28, /* false */ (0 << 29); // OtherNotAssigned = 29; private static bool IsWordSeparator(UnicodeCategory category) { return (wordSeparatorMask & (1 << (int)category)) != 0; } private static bool IsLetterCategory(UnicodeCategory uc) { return (uc == UnicodeCategory.UppercaseLetter || uc == UnicodeCategory.LowercaseLetter || uc == UnicodeCategory.TitlecaseLetter || uc == UnicodeCategory.ModifierLetter || uc == UnicodeCategory.OtherLetter); } #endif #if !FEATURE_CORECLR // IsRightToLeft // // Returns true if the dominant direction of text and UI such as the relative position of buttons and scroll bars // [System.Runtime.InteropServices.ComVisible(false)] public bool IsRightToLeft { [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] get { return this.m_cultureData.IsRightToLeft; } } #endif #if FEATURE_SERIALIZATION /// void IDeserializationCallback.OnDeserialization(Object sender) { OnDeserialized(); } #endif // // Get case-insensitive hash code for the specified string. // // NOTENOTE: this is an internal function. The caller should verify the string // is not null before calling this. Currenlty, CaseInsensitiveHashCodeProvider // does that. // [System.Security.SecuritySafeCritical] // auto-generated internal unsafe int GetCaseInsensitiveHashCode(String str) { return GetCaseInsensitiveHashCode(str, false, 0); } [System.Security.SecuritySafeCritical] // auto-generated internal unsafe int GetCaseInsensitiveHashCode(String str, bool forceRandomizedHashing, long additionalEntropy) { // Validate inputs if (str==null) { throw new ArgumentNullException("str"); } Contract.EndContractBlock(); // Return our result return (InternalGetCaseInsHash(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, str, forceRandomizedHashing, additionalEntropy)); } // Change case (ToUpper/ToLower) -- COMNlsInfo::InternalChangeCaseChar [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern char InternalChangeCaseChar(IntPtr handle, IntPtr handleOrigin, String localeName, char ch, bool isToUpper); // Change case (ToUpper/ToLower) -- COMNlsInfo::InternalChangeCaseString [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern String InternalChangeCaseString(IntPtr handle, IntPtr handleOrigin, String localeName, String str, bool isToUpper); // Get case insensitive hash -- ComNlsInfo::InternalGetCaseInsHash [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int InternalGetCaseInsHash(IntPtr handle, IntPtr handleOrigin, String localeName, String str, bool forceRandomizedHashing, long additionalEntropy); // Call ::CompareStringOrdinal -- ComNlsInfo::InternalCompareStringOrdinalIgnoreCase // Start at indexes and compare for length characters (or remainder of string if length == -1) [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] [SuppressUnmanagedCodeSecurity] private static unsafe extern int InternalCompareStringOrdinalIgnoreCase(String string1, int index1, String string2, int index2, int length1, int length2); // ComNlsInfo::InternalTryFindStringOrdinalIgnoreCase attempts a faster IndexOf/LastIndexOf OrdinalIgnoreCase using a kernel function. // Returns true if FindStringOrdinal was handled, with foundIndex set to the target's index into the source // Returns false when FindStringOrdinal wasn't handled [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] [SuppressUnmanagedCodeSecurity] [return: MarshalAs(UnmanagedType.Bool)] private static unsafe extern bool InternalTryFindStringOrdinalIgnoreCase(int searchFlags, String source, int sourceCount, int startIndex, String target, int targetCount, ref int foundIndex); } }