Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / whidbey / NetFxQFE / ndp / clr / src / BCL / System / Globalization / TextInfo.cs / 1 / TextInfo.cs
// ==++== // // 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 // //////////////////////////////////////////////////////////////////////////// namespace System.Globalization { using System; using System.Text; using System.Threading; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Security.Permissions; [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; // // Basically, this is the language ID (LANGID) used to call Win32 NLS APIs except that // the value can be zero for the invariant culture. // The reason for this data member to exist is that Win32 APIs // doesn't take all of the culture IDs supported in NLS+. // For example, NLS+ support culture IDs like 0x0000, 0x0009. // However, these are not valid locale IDs in Win32. Therefore, // we use a table to map a culutre ID like // 0x0009 to 0x0409. // // m_textInfoID should be either 0 or a supported language ID. See TextInfo(m_textInfoID) // for comments. [NonSerialized]private int m_textInfoID; [NonSerialized]private string m_name = null; [NonSerialized]private CultureTableRecord m_cultureTableRecord; [NonSerialized]private TextInfo m_casingTextInfo; // // m_pNativeTextInfo is a 32-bit pointer value pointing to a native C++ NativeTextInfo object. // The C++ NativeTextInfo is providing the implemenation of uppercasing/lowercasing. // // Note: m_pNativeTextInfo is intialized with invariant in case of synthetic cultuers [NonSerialized]private unsafe void *m_pNativeTextInfo; private static unsafe void* m_pInvariantNativeTextInfo; private static unsafe void* m_pDefaultCasingTable; // This file contains the default casing data (uppercasing/lowercasing/titlecasing), and the table to // map cultures with exceptions to an exception sub-table in CASING_EXCEPTIONS_FILE_NAME. private const String CASING_FILE_NAME = "l_intl.nlp"; // This file contains the casing data for cultures with exceptions. private const String CASING_EXCEPTIONS_FILE_NAME = "l_except.nlp"; // // This is the header for the native data table that we load from charinfo.nlp. // [StructLayout(LayoutKind.Explicit)] internal unsafe struct TextInfoDataHeader { [FieldOffset(0)] internal char TableName; // WCHAR[16] [FieldOffset(0x20)] internal ushort version; // WORD[4] [FieldOffset(0x28)] internal uint OffsetToUpperCasingTable; // DWORD [FieldOffset(0x2c)] internal uint OffsetToLowerCasingTable; // DWORD [FieldOffset(0x30)] internal uint OffsetToTitleCaseTable; // DWORD [FieldOffset(0x34)] internal uint PlaneOffset; // Each plane has DWORD offset for uppercase and DWORD offset for lowercase. // 0xb4 = 0x34 + 8*16 [FieldOffset(0xb4)] internal ushort exceptionCount; [FieldOffset(0xb6)] internal ushort exceptionLangId; } [StructLayout(LayoutKind.Sequential, Pack=2)] internal struct ExceptionTableItem { internal ushort langID; // The lcid that contains the exceptions. internal ushort exceptIndex; // The name of the exception tables. } // The base pointer of the defult casing table static unsafe byte* m_pDataTable; // The total count of cultures with exceptions. static int m_exceptionCount; // The pointer to the exception index table. This table maps a culture with exceptions // to a sub-table in the exception data table. static unsafe ExceptionTableItem* m_exceptionTable; // The base pointer for exception data file. static unsafe byte* m_pExceptionFile; // This array caches the native pointer of the NativeTextInfo get by calling InternalAllocateCasingTable. // NOTE: use long to hold native pointers. //static unsafe void*[] m_exceptionNativeTextInfo; static unsafe long[] m_exceptionNativeTextInfo; //////////////////////////////////////////////////////////////////////// // // Actions: // This is the static ctor for TextInfo. It does the following items: // * Get the total count of cultures with exceptions. // * Set up an exception index table so that we can check if a culture has exception. If yes, which sub-table // in the exception table file we should use for this culture. // * Set up a cache for NativeTextInfo that we create for cultures with exceptions. // //////////////////////////////////////////////////////////////////////// static unsafe TextInfo() { //with AppDomains active, the static initializer is no longer good enough to ensure that only one //thread is ever in AllocateDefaultCasingTable at a given time. //We use InterlockedExchangePointer in the native side to ensure that only one instance of native CasingTable instance //is created per process. //We check if the table is already allocated in native, so we only need to synchronize //access in managed. byte* temp = GlobalizationAssembly.GetGlobalizationResourceBytePtr(typeof(TextInfo).Assembly, CASING_FILE_NAME); System.Threading.Thread.MemoryBarrier(); m_pDataTable = temp; TextInfoDataHeader* pHeader = (TextInfoDataHeader*)m_pDataTable; m_exceptionCount = pHeader->exceptionCount; // Setup exception tables m_exceptionTable = (ExceptionTableItem*)&(pHeader->exceptionLangId); m_exceptionNativeTextInfo = new long[m_exceptionCount]; // Create the native NativeTextInfo for the default linguistic casing table. m_pDefaultCasingTable = AllocateDefaultCasingTable(m_pDataTable); BCLDebug.Assert(m_pDataTable != null, "Error in reading the table."); BCLDebug.Assert(m_pDefaultCasingTable != null, "m_pDefaultCasingTable != null"); } // Private object for locking instead of locking on a public type for SQL reliability work. private static Object s_InternalSyncObject; private static Object InternalSyncObject { get { if (s_InternalSyncObject == null) { Object o = new Object(); Interlocked.CompareExchange(ref s_InternalSyncObject, o, null); } return s_InternalSyncObject; } } //////////////////////////////////////////////////////////////////////// // // TextInfo Constructors // // Implements CultureInfo.TextInfo. // //////////////////////////////////////////////////////////////////////// internal unsafe TextInfo(CultureTableRecord table) { this.m_cultureTableRecord = table; this.m_textInfoID = this.m_cultureTableRecord.ITEXTINFO; if (table.IsSynthetic) { //// // we just initialize m_pNativeTextInfo with variant to make the synthetic TextInfo works when // GetCaseInsensitiveHashCode and ChangeCaseSurrogate get called. otherwise m_pNativeTextInfo // is not used at all in TextInfo with synthetic cultures. // m_pNativeTextInfo = InvariantNativeTextInfo; } else { this.m_pNativeTextInfo = GetNativeTextInfo(this.m_textInfoID); } } //////////////////////////////////////////////////////////////////////// // // Return the native TextInfo instance for the invariant culture. // //////////////////////////////////////////////////////////////////////// internal unsafe static void* InvariantNativeTextInfo { get { if (m_pInvariantNativeTextInfo == null) { lock(InternalSyncObject) { if (m_pInvariantNativeTextInfo == null) { m_pInvariantNativeTextInfo = GetNativeTextInfo(CultureInfo.LOCALE_INVARIANT); } } } BCLDebug.Assert(m_pInvariantNativeTextInfo != null, "TextInfo.InvariantNativeTextInfo: m_pInvariantNativeTextInfo != null"); return (m_pInvariantNativeTextInfo); } } #region Serialization // The following field is used only for the supplemental custom culture serialization to remember // the name of the custom culture so we can reconstruct the text info properly during the deserialization. [OptionalField(VersionAdded = 2)] private string customCultureName; // the following fields is defined to keep the compatibility with Everett. // don't change/remove the names/types of these fields. internal int m_nDataItem; internal bool m_useUserOverride; internal int m_win32LangID; [OnDeserializing] private void OnDeserializing(StreamingContext ctx) { m_cultureTableRecord = null; m_win32LangID = 0; } private unsafe void OnDeserialized() { // this method will be called twice because of the support of IDeserializationCallback if (m_cultureTableRecord == null) { // Due to our versioning, getting the CORRECT culture ID for an Everett structure is challenging. if (m_win32LangID == 0) { m_win32LangID = CultureTableRecord.IdFromEverettDataItem(m_nDataItem); } if (customCultureName != null) { m_cultureTableRecord = CultureTableRecord.GetCultureTableRecord(customCultureName, m_useUserOverride); } else { m_cultureTableRecord = CultureTableRecord.GetCultureTableRecord(m_win32LangID, m_useUserOverride); } m_textInfoID = m_cultureTableRecord.ITEXTINFO; if (m_cultureTableRecord.IsSynthetic) { // m_pNativeTextInfo = InvariantNativeTextInfo; } else { m_pNativeTextInfo = GetNativeTextInfo(m_textInfoID); } } } [OnDeserialized] private void OnDeserialized(StreamingContext ctx) { OnDeserialized(); } [OnSerializing] private void OnSerializing(StreamingContext ctx) { m_nDataItem = m_cultureTableRecord.EverettDataItem(); m_useUserOverride = m_cultureTableRecord.UseUserOverride; if (CultureTableRecord.IsCustomCultureId(m_cultureTableRecord.CultureID)) { customCultureName = m_cultureTableRecord.SNAME; m_win32LangID = m_textInfoID; } else { customCultureName = null; m_win32LangID = m_cultureTableRecord.CultureID; } } #endregion Serialization internal static unsafe void* GetNativeTextInfo(int cultureID) { #if !FEATURE_PAL if (cultureID == CultureInfo.LOCALE_INVARIANT && Environment.OSVersion.Platform == PlatformID.Win32NT) { void *pInvariantTextInfo = nativeGetInvariantTextInfo(); if (pInvariantTextInfo != null) { return pInvariantTextInfo; } throw new TypeInitializationException(typeof(System.Globalization.TextInfo).ToString(), null); } #endif // !FEATURE_PAL // First, assume this culture does not has exceptions. I.e. we should use the default casingg table. // So we assign the native NativeTextInfo for the default casing table to it. void *pNativeTextInfo = m_pDefaultCasingTable; // Now, go thru the exception table to see if it has exception or not. for (int i = 0; i < m_exceptionCount; i++) { if (m_exceptionTable[i].langID == cultureID) { // This culture has exceptions. if (m_exceptionNativeTextInfo[i] == 0) { lock (InternalSyncObject) { // Read the exception casing file. if (m_pExceptionFile == null) { m_pExceptionFile = GlobalizationAssembly.GetGlobalizationResourceBytePtr(typeof(TextInfo).Assembly, CASING_EXCEPTIONS_FILE_NAME); } long tempPtr = (long)(InternalAllocateCasingTable(m_pExceptionFile, m_exceptionTable[i].exceptIndex)); System.Threading.Thread.MemoryBarrier(); m_exceptionNativeTextInfo[i] = tempPtr; } } pNativeTextInfo = (void*)m_exceptionNativeTextInfo[i]; break; } } return (pNativeTextInfo); } internal static unsafe int CompareOrdinalIgnoreCase(String str1, String str2) { return (nativeCompareOrdinalIgnoreCase(InvariantNativeTextInfo, str1, str2)); } // This function doesn't check arguments. Please do check in the caller. // The underlying unmanaged code will assert the sanity of arguments. internal static unsafe int CompareOrdinalIgnoreCaseEx(String strA, int indexA, String strB, int indexB, int length ) { return (nativeCompareOrdinalIgnoreCaseEx(InvariantNativeTextInfo, strA, indexA, strB, indexB, length)); } // This function doesn't check arguments. Please do check in the caller. // The underlying unmanaged code will assert the sanity of arguments. internal static unsafe int GetHashCodeOrdinalIgnoreCase(String s) { return (nativeGetHashCodeOrdinalIgnoreCase(InvariantNativeTextInfo, s)); } internal static unsafe int IndexOfStringOrdinalIgnoreCase(String source, String value, int startIndex, int count) { if (source == null) throw new ArgumentNullException("source"); return nativeIndexOfStringOrdinalIgnoreCase(InvariantNativeTextInfo, source, value, startIndex, count); } internal static unsafe int LastIndexOfStringOrdinalIgnoreCase(String source, String value, int startIndex, int count) { if (source == null) throw new ArgumentNullException("source"); return nativeLastIndexOfStringOrdinalIgnoreCase(InvariantNativeTextInfo, source, value, startIndex, count); } //////////////////////////////////////////////////////////////////////// // // 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 // //////////////////////////////////////////////////////////////////////// public virtual int ANSICodePage { get { return (this.m_cultureTableRecord.IDEFAULTANSICODEPAGE); } } public virtual int OEMCodePage { get { return (this.m_cultureTableRecord.IDEFAULTOEMCODEPAGE); } } public virtual int MacCodePage { get { return (this.m_cultureTableRecord.IDEFAULTMACCODEPAGE); } } public virtual int EBCDICCodePage { get { return (this.m_cultureTableRecord.IDEFAULTEBCDICCODEPAGE); } } //////////////////////////////////////////////////////////////////////// // // LCID // // We need a way to get an LCID from outside of the BCL. This prop is the way. // // neutral cultures will cause GPS incorrect LCIDS from this //////////////////////////////////////////////////////////////////////// [System.Runtime.InteropServices.ComVisible(false)] public int LCID { get { return (this.m_textInfoID); } } //////////////////////////////////////////////////////////////////////// // // CultureName // // The name of the culture from which the TextInfo was created. Even better than // the LCID since the LCID is not always unique (like in custom cultures). // //////////////////////////////////////////////////////////////////////// [System.Runtime.InteropServices.ComVisible(false)] public string CultureName { get { if(null == this.m_name) { this.m_name = CultureInfo.GetCultureInfo(this.m_textInfoID).Name; } return(this.m_name); } } //////////////////////////////////////////////////////////////////////// // // 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"); } 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")); } } internal void SetReadOnlyState(bool readOnly) { m_isReadOnly = readOnly; } //////////////////////////////////////////////////////////////////////// // // ListSeparator // // Returns the string used to separate items in a list. // //////////////////////////////////////////////////////////////////////// public virtual String ListSeparator { get { if (m_listSeparator == null) { m_listSeparator = this.m_cultureTableRecord.SLIST; } return (m_listSeparator); } [System.Runtime.InteropServices.ComVisible(false)] set { VerifyWritable(); if (value == null) { throw new ArgumentNullException("value", Environment.GetResourceString("ArgumentNull_String")); } m_listSeparator = value; } } internal TextInfo CasingTextInfo { get { if (m_casingTextInfo == null) { if (ANSICodePage == TurkishAnsiCodepage) { // Turkish cultures have different uppercase and lowercase for 'i' and 'I' characters. so we need // to treat it as special case. m_casingTextInfo = CultureInfo.GetCultureInfo("tr-TR").TextInfo; } else { m_casingTextInfo = CultureInfo.GetCultureInfo("en-US").TextInfo; } } return m_casingTextInfo; } } //////////////////////////////////////////////////////////////////////// // // ToLower // // Converts the character or string to lower case. Certain locales // have different casing semantics from the file systems in Win32. // //////////////////////////////////////////////////////////////////////// public unsafe virtual char ToLower(char c) { #if !FEATURE_PAL // if (m_cultureTableRecord.IsSynthetic) { return CasingTextInfo.ToLower(c); } #endif // !FEATURE_PAL return (nativeChangeCaseChar(m_textInfoID, m_pNativeTextInfo, c, false)); } public unsafe virtual String ToLower(String str) { if (str == null) { throw new ArgumentNullException("str"); } #if !FEATURE_PAL // if (m_cultureTableRecord.IsSynthetic) { return CasingTextInfo.ToLower(str); } #endif // !FEATURE_PAL return (nativeChangeCaseString(m_textInfoID, m_pNativeTextInfo, str, false)); } //////////////////////////////////////////////////////////////////////// // // ToUpper // // Converts the character or string to upper case. Certain locales // have different casing semantics from the file systems in Win32. // //////////////////////////////////////////////////////////////////////// public unsafe virtual char ToUpper(char c) { #if !FEATURE_PAL // if (m_cultureTableRecord.IsSynthetic) { return CasingTextInfo.ToUpper(c); } #endif // !FEATURE_PAL return (nativeChangeCaseChar(m_textInfoID, m_pNativeTextInfo, c, true)); } public unsafe virtual String ToUpper(String str) { if (str == null) { throw new ArgumentNullException("str"); } #if !FEATURE_PAL // if (m_cultureTableRecord.IsSynthetic) { return CasingTextInfo.ToUpper(str); } #endif // !FEATURE_PAL return (nativeChangeCaseString(m_textInfoID, m_pNativeTextInfo, str, true)); } //////////////////////////////////////////////////////////////////////// // // 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_textInfoID); } // // 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 bool IsWordSeparator(UnicodeCategory category) { return (wordSeparatorMask & (1 << (int)category)) != 0; } // Returns the mapping of the specified string to title case. Note that the // returned string may differ in length from the input string. // Generally, the first character of every word in str is uppercased. // For titlecase characters, they are uppercased in a specail way. internal const int TurkishAnsiCodepage = 1254; public unsafe String ToTitleCase(String str) { if (str==null) { throw new ArgumentNullException("str"); } #if !FEATURE_PAL if (m_cultureTableRecord.IsSynthetic) { // if (ANSICodePage == TurkishAnsiCodepage) { // Turkish cultures have different uppercase and lowercase for 'i' and 'I' characters. so we need // to treat it as special case. return CultureInfo.GetCultureInfo("tr-TR").TextInfo.ToTitleCase(str); } return CultureInfo.GetCultureInfo("en-US").TextInfo.ToTitleCase(str); } #endif // !FEATURE_PAL int len = str.Length; if (len == 0) { return (str); } int i; StringBuilder result = new StringBuilder(); String lowercaseData = null; for (i = 0; i < len; i++) { UnicodeCategory charType; int charLen; charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen); if (Char.CheckLetter(charType)) { // Do the uppercasing for the first character of the word. // There are titlecase characters that need to be special treated. if (charLen == 1) { result.Append(nativeGetTitleCaseChar(m_pNativeTextInfo, str[i])); } else { // // ASSUMPTION: There is no titlecase char in the surrogate. // char resultHighSurrogate; char resultLowSurrogate; ChangeCaseSurrogate(str[i], str[i+1], out resultHighSurrogate, out resultLowSurrogate, true); result.Append(resultHighSurrogate); result.Append(resultLowSurrogate); } i+= charLen; // // 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 titilecasing. // bool hasLowerCase = (charType == UnicodeCategory.LowercaseLetter); // Use a loop to find all of the other letters following this letter. while (i < len) { charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen); if (IsLetterCategory(charType)) { if (charType == UnicodeCategory.LowercaseLetter) { hasLowerCase = true; } i += charLen; } else if (str[i] == '\'') { // Special case for APOSTROPHE. It should be considered part of the word. E.g. "can't". 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 < len) { // Add the non-letter character. if (charLen == 1) { result.Append(str[i]); } else { // Surrogate. result.Append(str[i++]); result.Append(str[i]); } } } else { // // Not a letter, just append them. // if (charLen == 1) { result.Append(str[i]); } else { // Surrogate. result.Append(str[i++]); result.Append(str[i]); } } } return (result.ToString()); } // 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 { get { // The highest bit indicates writing order for left-to-right (0) or right-to-left (1) return ((this.m_cultureTableRecord.ILINEORIENTATIONS & 0x8000) != 0); } } private bool IsLetterCategory(UnicodeCategory uc) { return (uc == UnicodeCategory.UppercaseLetter || uc == UnicodeCategory.LowercaseLetter || uc == UnicodeCategory.TitlecaseLetter || uc == UnicodeCategory.ModifierLetter || uc == UnicodeCategory.OtherLetter); } /// void IDeserializationCallback.OnDeserialization(Object sender) { OnDeserialized(); } // // 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. // internal unsafe int GetCaseInsensitiveHashCode(String str) { // This must be called to guarantee m_pNativeTextInfo is initialized. // The reason is that the order of calling OnDeserializtion on dependent // objects are not guaranteed, so a class using // TextInfo class (Hashtable is an example) will have problems in // its deserializtion process if methods of TextInfo class is called in the // deserialization process. // if (str==null) { throw new ArgumentNullException("str"); } if (m_pNativeTextInfo == null) { OnDeserialized(); } // This is the fix to avoid introduce // a dependency on mscorlib.dll and mscorwks.dll, which the real fix needs. // By doing this, we will do uppercase twice for Turkish/Azeri, so it is slower // in these two cultures. The benefit is that we only have to do the fix in the managed side. switch (m_textInfoID) { case 0x041f: // Turkish case 0x042c: // Azeri // Uppercase the specified characters. str = nativeChangeCaseString(m_textInfoID, m_pNativeTextInfo, str, true); break; } return (nativeGetCaseInsHash(str, m_pNativeTextInfo)); // A better fix is to exam the m_wing32LangID and the high-char state in the native side to decide if we can do "fast hashing". //return nativeGetCaseInsHash(m_textInfoID, str, m_pNativeTextInfo); } // A thin wrapper to avoid us to mark ToTitleCase() as unsafe. internal unsafe void ChangeCaseSurrogate(char highSurrogate, char lowSurrogate, out char resultHighSurrogate, out char resultLowSurrogate, bool isToUpper) { fixed (char* pResultChar1 = &resultHighSurrogate, pResultChar2 = &resultLowSurrogate) { nativeChangeCaseSurrogate(m_pNativeTextInfo, highSurrogate, lowSurrogate, pResultChar1, pResultChar2, isToUpper); } } //This method requires synchronization and should only be called from the Class Initializer. [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern void* AllocateDefaultCasingTable(byte* ptr); //This method requires synchronization and should only be called from the Class Initializer. [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern void* nativeGetInvariantTextInfo(); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern void* InternalAllocateCasingTable(byte* ptr, int exceptionIndex); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeGetCaseInsHash(String str, void* pNativeTextInfo); // private static extern int nativeGetCaseInsHash(int win32LangID, String str, void* pNativeTextInfo); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern char nativeGetTitleCaseChar(void*pNativeTextInfo, char ch); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static unsafe extern char nativeChangeCaseChar(int win32LangID, void *pNativeTextInfo, char ch, bool isToUpper); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static unsafe extern String nativeChangeCaseString(int win32LangID, void*pNativeTextInfo, String str, bool isToUpper); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static unsafe extern void nativeChangeCaseSurrogate(void *pNativeTextInfo, char highSurrogate, char lowSurrogate, char* resultHighSurrogate, char* resultLowSurrogate, bool isToUpper); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeCompareOrdinalIgnoreCase(void* pNativeTextInfo, String str1, String str2); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeCompareOrdinalIgnoreCaseEx(void* pNativeTextInfo, String strA, int indexA, String strB, int indexB, int length); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeGetHashCodeOrdinalIgnoreCase(void* pNativeTextInfo, String s); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeIndexOfStringOrdinalIgnoreCase(void* pNativeTextInfo, String str, String value, int startIndex, int count); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeLastIndexOfStringOrdinalIgnoreCase(void* pNativeTextInfo, String str, String value, int startIndex, int count); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static unsafe extern int nativeIndexOfCharOrdinalIgnoreCase(void* pNativeTextInfo, String str, Char value, int startIndex, int count); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static unsafe extern int nativeLastIndexOfCharOrdinalIgnoreCase(void* pNativeTextInfo, String str, Char value, int startIndex, int count); } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // ==++== // // 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 // //////////////////////////////////////////////////////////////////////////// namespace System.Globalization { using System; using System.Text; using System.Threading; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Security.Permissions; [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; // // Basically, this is the language ID (LANGID) used to call Win32 NLS APIs except that // the value can be zero for the invariant culture. // The reason for this data member to exist is that Win32 APIs // doesn't take all of the culture IDs supported in NLS+. // For example, NLS+ support culture IDs like 0x0000, 0x0009. // However, these are not valid locale IDs in Win32. Therefore, // we use a table to map a culutre ID like // 0x0009 to 0x0409. // // m_textInfoID should be either 0 or a supported language ID. See TextInfo(m_textInfoID) // for comments. [NonSerialized]private int m_textInfoID; [NonSerialized]private string m_name = null; [NonSerialized]private CultureTableRecord m_cultureTableRecord; [NonSerialized]private TextInfo m_casingTextInfo; // // m_pNativeTextInfo is a 32-bit pointer value pointing to a native C++ NativeTextInfo object. // The C++ NativeTextInfo is providing the implemenation of uppercasing/lowercasing. // // Note: m_pNativeTextInfo is intialized with invariant in case of synthetic cultuers [NonSerialized]private unsafe void *m_pNativeTextInfo; private static unsafe void* m_pInvariantNativeTextInfo; private static unsafe void* m_pDefaultCasingTable; // This file contains the default casing data (uppercasing/lowercasing/titlecasing), and the table to // map cultures with exceptions to an exception sub-table in CASING_EXCEPTIONS_FILE_NAME. private const String CASING_FILE_NAME = "l_intl.nlp"; // This file contains the casing data for cultures with exceptions. private const String CASING_EXCEPTIONS_FILE_NAME = "l_except.nlp"; // // This is the header for the native data table that we load from charinfo.nlp. // [StructLayout(LayoutKind.Explicit)] internal unsafe struct TextInfoDataHeader { [FieldOffset(0)] internal char TableName; // WCHAR[16] [FieldOffset(0x20)] internal ushort version; // WORD[4] [FieldOffset(0x28)] internal uint OffsetToUpperCasingTable; // DWORD [FieldOffset(0x2c)] internal uint OffsetToLowerCasingTable; // DWORD [FieldOffset(0x30)] internal uint OffsetToTitleCaseTable; // DWORD [FieldOffset(0x34)] internal uint PlaneOffset; // Each plane has DWORD offset for uppercase and DWORD offset for lowercase. // 0xb4 = 0x34 + 8*16 [FieldOffset(0xb4)] internal ushort exceptionCount; [FieldOffset(0xb6)] internal ushort exceptionLangId; } [StructLayout(LayoutKind.Sequential, Pack=2)] internal struct ExceptionTableItem { internal ushort langID; // The lcid that contains the exceptions. internal ushort exceptIndex; // The name of the exception tables. } // The base pointer of the defult casing table static unsafe byte* m_pDataTable; // The total count of cultures with exceptions. static int m_exceptionCount; // The pointer to the exception index table. This table maps a culture with exceptions // to a sub-table in the exception data table. static unsafe ExceptionTableItem* m_exceptionTable; // The base pointer for exception data file. static unsafe byte* m_pExceptionFile; // This array caches the native pointer of the NativeTextInfo get by calling InternalAllocateCasingTable. // NOTE: use long to hold native pointers. //static unsafe void*[] m_exceptionNativeTextInfo; static unsafe long[] m_exceptionNativeTextInfo; //////////////////////////////////////////////////////////////////////// // // Actions: // This is the static ctor for TextInfo. It does the following items: // * Get the total count of cultures with exceptions. // * Set up an exception index table so that we can check if a culture has exception. If yes, which sub-table // in the exception table file we should use for this culture. // * Set up a cache for NativeTextInfo that we create for cultures with exceptions. // //////////////////////////////////////////////////////////////////////// static unsafe TextInfo() { //with AppDomains active, the static initializer is no longer good enough to ensure that only one //thread is ever in AllocateDefaultCasingTable at a given time. //We use InterlockedExchangePointer in the native side to ensure that only one instance of native CasingTable instance //is created per process. //We check if the table is already allocated in native, so we only need to synchronize //access in managed. byte* temp = GlobalizationAssembly.GetGlobalizationResourceBytePtr(typeof(TextInfo).Assembly, CASING_FILE_NAME); System.Threading.Thread.MemoryBarrier(); m_pDataTable = temp; TextInfoDataHeader* pHeader = (TextInfoDataHeader*)m_pDataTable; m_exceptionCount = pHeader->exceptionCount; // Setup exception tables m_exceptionTable = (ExceptionTableItem*)&(pHeader->exceptionLangId); m_exceptionNativeTextInfo = new long[m_exceptionCount]; // Create the native NativeTextInfo for the default linguistic casing table. m_pDefaultCasingTable = AllocateDefaultCasingTable(m_pDataTable); BCLDebug.Assert(m_pDataTable != null, "Error in reading the table."); BCLDebug.Assert(m_pDefaultCasingTable != null, "m_pDefaultCasingTable != null"); } // Private object for locking instead of locking on a public type for SQL reliability work. private static Object s_InternalSyncObject; private static Object InternalSyncObject { get { if (s_InternalSyncObject == null) { Object o = new Object(); Interlocked.CompareExchange(ref s_InternalSyncObject, o, null); } return s_InternalSyncObject; } } //////////////////////////////////////////////////////////////////////// // // TextInfo Constructors // // Implements CultureInfo.TextInfo. // //////////////////////////////////////////////////////////////////////// internal unsafe TextInfo(CultureTableRecord table) { this.m_cultureTableRecord = table; this.m_textInfoID = this.m_cultureTableRecord.ITEXTINFO; if (table.IsSynthetic) { // // // we just initialize m_pNativeTextInfo with variant to make the synthetic TextInfo works when // GetCaseInsensitiveHashCode and ChangeCaseSurrogate get called. otherwise m_pNativeTextInfo // is not used at all in TextInfo with synthetic cultures. // m_pNativeTextInfo = InvariantNativeTextInfo; } else { this.m_pNativeTextInfo = GetNativeTextInfo(this.m_textInfoID); } } //////////////////////////////////////////////////////////////////////// // // Return the native TextInfo instance for the invariant culture. // //////////////////////////////////////////////////////////////////////// internal unsafe static void* InvariantNativeTextInfo { get { if (m_pInvariantNativeTextInfo == null) { lock(InternalSyncObject) { if (m_pInvariantNativeTextInfo == null) { m_pInvariantNativeTextInfo = GetNativeTextInfo(CultureInfo.LOCALE_INVARIANT); } } } BCLDebug.Assert(m_pInvariantNativeTextInfo != null, "TextInfo.InvariantNativeTextInfo: m_pInvariantNativeTextInfo != null"); return (m_pInvariantNativeTextInfo); } } #region Serialization // The following field is used only for the supplemental custom culture serialization to remember // the name of the custom culture so we can reconstruct the text info properly during the deserialization. [OptionalField(VersionAdded = 2)] private string customCultureName; // the following fields is defined to keep the compatibility with Everett. // don't change/remove the names/types of these fields. internal int m_nDataItem; internal bool m_useUserOverride; internal int m_win32LangID; [OnDeserializing] private void OnDeserializing(StreamingContext ctx) { m_cultureTableRecord = null; m_win32LangID = 0; } private unsafe void OnDeserialized() { // this method will be called twice because of the support of IDeserializationCallback if (m_cultureTableRecord == null) { // Due to our versioning, getting the CORRECT culture ID for an Everett structure is challenging. if (m_win32LangID == 0) { m_win32LangID = CultureTableRecord.IdFromEverettDataItem(m_nDataItem); } if (customCultureName != null) { m_cultureTableRecord = CultureTableRecord.GetCultureTableRecord(customCultureName, m_useUserOverride); } else { m_cultureTableRecord = CultureTableRecord.GetCultureTableRecord(m_win32LangID, m_useUserOverride); } m_textInfoID = m_cultureTableRecord.ITEXTINFO; if (m_cultureTableRecord.IsSynthetic) { // m_pNativeTextInfo = InvariantNativeTextInfo; } else { m_pNativeTextInfo = GetNativeTextInfo(m_textInfoID); } } } [OnDeserialized] private void OnDeserialized(StreamingContext ctx) { OnDeserialized(); } [OnSerializing] private void OnSerializing(StreamingContext ctx) { m_nDataItem = m_cultureTableRecord.EverettDataItem(); m_useUserOverride = m_cultureTableRecord.UseUserOverride; if (CultureTableRecord.IsCustomCultureId(m_cultureTableRecord.CultureID)) { customCultureName = m_cultureTableRecord.SNAME; m_win32LangID = m_textInfoID; } else { customCultureName = null; m_win32LangID = m_cultureTableRecord.CultureID; } } #endregion Serialization internal static unsafe void* GetNativeTextInfo(int cultureID) { #if !FEATURE_PAL if (cultureID == CultureInfo.LOCALE_INVARIANT && Environment.OSVersion.Platform == PlatformID.Win32NT) { void *pInvariantTextInfo = nativeGetInvariantTextInfo(); if (pInvariantTextInfo != null) { return pInvariantTextInfo; } throw new TypeInitializationException(typeof(System.Globalization.TextInfo).ToString(), null); } #endif // !FEATURE_PAL // First, assume this culture does not has exceptions. I.e. we should use the default casingg table. // So we assign the native NativeTextInfo for the default casing table to it. void *pNativeTextInfo = m_pDefaultCasingTable; // Now, go thru the exception table to see if it has exception or not. for (int i = 0; i < m_exceptionCount; i++) { if (m_exceptionTable[i].langID == cultureID) { // This culture has exceptions. if (m_exceptionNativeTextInfo[i] == 0) { lock (InternalSyncObject) { // Read the exception casing file. if (m_pExceptionFile == null) { m_pExceptionFile = GlobalizationAssembly.GetGlobalizationResourceBytePtr(typeof(TextInfo).Assembly, CASING_EXCEPTIONS_FILE_NAME); } long tempPtr = (long)(InternalAllocateCasingTable(m_pExceptionFile, m_exceptionTable[i].exceptIndex)); System.Threading.Thread.MemoryBarrier(); m_exceptionNativeTextInfo[i] = tempPtr; } } pNativeTextInfo = (void*)m_exceptionNativeTextInfo[i]; break; } } return (pNativeTextInfo); } internal static unsafe int CompareOrdinalIgnoreCase(String str1, String str2) { return (nativeCompareOrdinalIgnoreCase(InvariantNativeTextInfo, str1, str2)); } // This function doesn't check arguments. Please do check in the caller. // The underlying unmanaged code will assert the sanity of arguments. internal static unsafe int CompareOrdinalIgnoreCaseEx(String strA, int indexA, String strB, int indexB, int length ) { return (nativeCompareOrdinalIgnoreCaseEx(InvariantNativeTextInfo, strA, indexA, strB, indexB, length)); } // This function doesn't check arguments. Please do check in the caller. // The underlying unmanaged code will assert the sanity of arguments. internal static unsafe int GetHashCodeOrdinalIgnoreCase(String s) { return (nativeGetHashCodeOrdinalIgnoreCase(InvariantNativeTextInfo, s)); } internal static unsafe int IndexOfStringOrdinalIgnoreCase(String source, String value, int startIndex, int count) { if (source == null) throw new ArgumentNullException("source"); return nativeIndexOfStringOrdinalIgnoreCase(InvariantNativeTextInfo, source, value, startIndex, count); } internal static unsafe int LastIndexOfStringOrdinalIgnoreCase(String source, String value, int startIndex, int count) { if (source == null) throw new ArgumentNullException("source"); return nativeLastIndexOfStringOrdinalIgnoreCase(InvariantNativeTextInfo, source, value, startIndex, count); } //////////////////////////////////////////////////////////////////////// // // 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 // //////////////////////////////////////////////////////////////////////// public virtual int ANSICodePage { get { return (this.m_cultureTableRecord.IDEFAULTANSICODEPAGE); } } public virtual int OEMCodePage { get { return (this.m_cultureTableRecord.IDEFAULTOEMCODEPAGE); } } public virtual int MacCodePage { get { return (this.m_cultureTableRecord.IDEFAULTMACCODEPAGE); } } public virtual int EBCDICCodePage { get { return (this.m_cultureTableRecord.IDEFAULTEBCDICCODEPAGE); } } //////////////////////////////////////////////////////////////////////// // // LCID // // We need a way to get an LCID from outside of the BCL. This prop is the way. // // neutral cultures will cause GPS incorrect LCIDS from this //////////////////////////////////////////////////////////////////////// [System.Runtime.InteropServices.ComVisible(false)] public int LCID { get { return (this.m_textInfoID); } } //////////////////////////////////////////////////////////////////////// // // CultureName // // The name of the culture from which the TextInfo was created. Even better than // the LCID since the LCID is not always unique (like in custom cultures). // //////////////////////////////////////////////////////////////////////// [System.Runtime.InteropServices.ComVisible(false)] public string CultureName { get { if(null == this.m_name) { this.m_name = CultureInfo.GetCultureInfo(this.m_textInfoID).Name; } return(this.m_name); } } //////////////////////////////////////////////////////////////////////// // // 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"); } 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")); } } internal void SetReadOnlyState(bool readOnly) { m_isReadOnly = readOnly; } //////////////////////////////////////////////////////////////////////// // // ListSeparator // // Returns the string used to separate items in a list. // //////////////////////////////////////////////////////////////////////// public virtual String ListSeparator { get { if (m_listSeparator == null) { m_listSeparator = this.m_cultureTableRecord.SLIST; } return (m_listSeparator); } [System.Runtime.InteropServices.ComVisible(false)] set { VerifyWritable(); if (value == null) { throw new ArgumentNullException("value", Environment.GetResourceString("ArgumentNull_String")); } m_listSeparator = value; } } internal TextInfo CasingTextInfo { get { if (m_casingTextInfo == null) { if (ANSICodePage == TurkishAnsiCodepage) { // Turkish cultures have different uppercase and lowercase for 'i' and 'I' characters. so we need // to treat it as special case. m_casingTextInfo = CultureInfo.GetCultureInfo("tr-TR").TextInfo; } else { m_casingTextInfo = CultureInfo.GetCultureInfo("en-US").TextInfo; } } return m_casingTextInfo; } } //////////////////////////////////////////////////////////////////////// // // ToLower // // Converts the character or string to lower case. Certain locales // have different casing semantics from the file systems in Win32. // //////////////////////////////////////////////////////////////////////// public unsafe virtual char ToLower(char c) { #if !FEATURE_PAL // if (m_cultureTableRecord.IsSynthetic) { return CasingTextInfo.ToLower(c); } #endif // !FEATURE_PAL return (nativeChangeCaseChar(m_textInfoID, m_pNativeTextInfo, c, false)); } public unsafe virtual String ToLower(String str) { if (str == null) { throw new ArgumentNullException("str"); } #if !FEATURE_PAL // if (m_cultureTableRecord.IsSynthetic) { return CasingTextInfo.ToLower(str); } #endif // !FEATURE_PAL return (nativeChangeCaseString(m_textInfoID, m_pNativeTextInfo, str, false)); } //////////////////////////////////////////////////////////////////////// // // ToUpper // // Converts the character or string to upper case. Certain locales // have different casing semantics from the file systems in Win32. // //////////////////////////////////////////////////////////////////////// public unsafe virtual char ToUpper(char c) { #if !FEATURE_PAL // if (m_cultureTableRecord.IsSynthetic) { return CasingTextInfo.ToUpper(c); } #endif // !FEATURE_PAL return (nativeChangeCaseChar(m_textInfoID, m_pNativeTextInfo, c, true)); } public unsafe virtual String ToUpper(String str) { if (str == null) { throw new ArgumentNullException("str"); } #if !FEATURE_PAL // if (m_cultureTableRecord.IsSynthetic) { return CasingTextInfo.ToUpper(str); } #endif // !FEATURE_PAL return (nativeChangeCaseString(m_textInfoID, m_pNativeTextInfo, str, true)); } //////////////////////////////////////////////////////////////////////// // // 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_textInfoID); } // // 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 bool IsWordSeparator(UnicodeCategory category) { return (wordSeparatorMask & (1 << (int)category)) != 0; } // Returns the mapping of the specified string to title case. Note that the // returned string may differ in length from the input string. // Generally, the first character of every word in str is uppercased. // For titlecase characters, they are uppercased in a specail way. internal const int TurkishAnsiCodepage = 1254; public unsafe String ToTitleCase(String str) { if (str==null) { throw new ArgumentNullException("str"); } #if !FEATURE_PAL if (m_cultureTableRecord.IsSynthetic) { // if (ANSICodePage == TurkishAnsiCodepage) { // Turkish cultures have different uppercase and lowercase for 'i' and 'I' characters. so we need // to treat it as special case. return CultureInfo.GetCultureInfo("tr-TR").TextInfo.ToTitleCase(str); } return CultureInfo.GetCultureInfo("en-US").TextInfo.ToTitleCase(str); } #endif // !FEATURE_PAL int len = str.Length; if (len == 0) { return (str); } int i; StringBuilder result = new StringBuilder(); String lowercaseData = null; for (i = 0; i < len; i++) { UnicodeCategory charType; int charLen; charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen); if (Char.CheckLetter(charType)) { // Do the uppercasing for the first character of the word. // There are titlecase characters that need to be special treated. if (charLen == 1) { result.Append(nativeGetTitleCaseChar(m_pNativeTextInfo, str[i])); } else { // // ASSUMPTION: There is no titlecase char in the surrogate. // char resultHighSurrogate; char resultLowSurrogate; ChangeCaseSurrogate(str[i], str[i+1], out resultHighSurrogate, out resultLowSurrogate, true); result.Append(resultHighSurrogate); result.Append(resultLowSurrogate); } i+= charLen; // // 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 titilecasing. // bool hasLowerCase = (charType == UnicodeCategory.LowercaseLetter); // Use a loop to find all of the other letters following this letter. while (i < len) { charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen); if (IsLetterCategory(charType)) { if (charType == UnicodeCategory.LowercaseLetter) { hasLowerCase = true; } i += charLen; } else if (str[i] == '\'') { // Special case for APOSTROPHE. It should be considered part of the word. E.g. "can't". 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 < len) { // Add the non-letter character. if (charLen == 1) { result.Append(str[i]); } else { // Surrogate. result.Append(str[i++]); result.Append(str[i]); } } } else { // // Not a letter, just append them. // if (charLen == 1) { result.Append(str[i]); } else { // Surrogate. result.Append(str[i++]); result.Append(str[i]); } } } return (result.ToString()); } // 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 { get { // The highest bit indicates writing order for left-to-right (0) or right-to-left (1) return ((this.m_cultureTableRecord.ILINEORIENTATIONS & 0x8000) != 0); } } private bool IsLetterCategory(UnicodeCategory uc) { return (uc == UnicodeCategory.UppercaseLetter || uc == UnicodeCategory.LowercaseLetter || uc == UnicodeCategory.TitlecaseLetter || uc == UnicodeCategory.ModifierLetter || uc == UnicodeCategory.OtherLetter); } /// void IDeserializationCallback.OnDeserialization(Object sender) { OnDeserialized(); } // // 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. // internal unsafe int GetCaseInsensitiveHashCode(String str) { // This must be called to guarantee m_pNativeTextInfo is initialized. // The reason is that the order of calling OnDeserializtion on dependent // objects are not guaranteed, so a class using // TextInfo class (Hashtable is an example) will have problems in // its deserializtion process if methods of TextInfo class is called in the // deserialization process. // if (str==null) { throw new ArgumentNullException("str"); } if (m_pNativeTextInfo == null) { OnDeserialized(); } // This is the fix to avoid introduce // a dependency on mscorlib.dll and mscorwks.dll, which the real fix needs. // By doing this, we will do uppercase twice for Turkish/Azeri, so it is slower // in these two cultures. The benefit is that we only have to do the fix in the managed side. switch (m_textInfoID) { case 0x041f: // Turkish case 0x042c: // Azeri // Uppercase the specified characters. str = nativeChangeCaseString(m_textInfoID, m_pNativeTextInfo, str, true); break; } return (nativeGetCaseInsHash(str, m_pNativeTextInfo)); // A better fix is to exam the m_wing32LangID and the high-char state in the native side to decide if we can do "fast hashing". //return nativeGetCaseInsHash(m_textInfoID, str, m_pNativeTextInfo); } // A thin wrapper to avoid us to mark ToTitleCase() as unsafe. internal unsafe void ChangeCaseSurrogate(char highSurrogate, char lowSurrogate, out char resultHighSurrogate, out char resultLowSurrogate, bool isToUpper) { fixed (char* pResultChar1 = &resultHighSurrogate, pResultChar2 = &resultLowSurrogate) { nativeChangeCaseSurrogate(m_pNativeTextInfo, highSurrogate, lowSurrogate, pResultChar1, pResultChar2, isToUpper); } } //This method requires synchronization and should only be called from the Class Initializer. [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern void* AllocateDefaultCasingTable(byte* ptr); //This method requires synchronization and should only be called from the Class Initializer. [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern void* nativeGetInvariantTextInfo(); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern void* InternalAllocateCasingTable(byte* ptr, int exceptionIndex); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeGetCaseInsHash(String str, void* pNativeTextInfo); // private static extern int nativeGetCaseInsHash(int win32LangID, String str, void* pNativeTextInfo); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern char nativeGetTitleCaseChar(void*pNativeTextInfo, char ch); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static unsafe extern char nativeChangeCaseChar(int win32LangID, void *pNativeTextInfo, char ch, bool isToUpper); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static unsafe extern String nativeChangeCaseString(int win32LangID, void*pNativeTextInfo, String str, bool isToUpper); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static unsafe extern void nativeChangeCaseSurrogate(void *pNativeTextInfo, char highSurrogate, char lowSurrogate, char* resultHighSurrogate, char* resultLowSurrogate, bool isToUpper); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeCompareOrdinalIgnoreCase(void* pNativeTextInfo, String str1, String str2); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeCompareOrdinalIgnoreCaseEx(void* pNativeTextInfo, String strA, int indexA, String strB, int indexB, int length); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeGetHashCodeOrdinalIgnoreCase(void* pNativeTextInfo, String s); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeIndexOfStringOrdinalIgnoreCase(void* pNativeTextInfo, String str, String value, int startIndex, int count); [MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern int nativeLastIndexOfStringOrdinalIgnoreCase(void* pNativeTextInfo, String str, String value, int startIndex, int count); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static unsafe extern int nativeIndexOfCharOrdinalIgnoreCase(void* pNativeTextInfo, String str, Char value, int startIndex, int count); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static unsafe extern int nativeLastIndexOfCharOrdinalIgnoreCase(void* pNativeTextInfo, String str, Char value, int startIndex, int count); } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ColumnResult.cs
- SystemWebSectionGroup.cs
- OperationAbortedException.cs
- MachineKeySection.cs
- BridgeDataRecord.cs
- Converter.cs
- MappingModelBuildProvider.cs
- RegisteredArrayDeclaration.cs
- SortedList.cs
- VBIdentifierDesigner.xaml.cs
- ObjectHandle.cs
- StringWriter.cs
- bindurihelper.cs
- NodeFunctions.cs
- SourceElementsCollection.cs
- LinkConverter.cs
- XmlDictionaryReader.cs
- ValidationRuleCollection.cs
- RsaSecurityTokenParameters.cs
- PropertyInformation.cs
- CodeAccessPermission.cs
- LocalizableAttribute.cs
- control.ime.cs
- TagPrefixInfo.cs
- CreateUserErrorEventArgs.cs
- EditBehavior.cs
- DataListItemCollection.cs
- ToolStripPanelSelectionBehavior.cs
- FrugalMap.cs
- ComponentChangedEvent.cs
- ComplexPropertyEntry.cs
- ContractTypeNameElement.cs
- LingerOption.cs
- WebAdminConfigurationHelper.cs
- TextCompositionManager.cs
- EventSinkHelperWriter.cs
- MenuItemCollection.cs
- RequestCacheEntry.cs
- ManagedIStream.cs
- OperationFormatStyle.cs
- SurrogateEncoder.cs
- ReadOnlyCollection.cs
- ExternalDataExchangeService.cs
- BinaryQueryOperator.cs
- GeneratedContractType.cs
- SqlDataSourceCache.cs
- IUnknownConstantAttribute.cs
- Helper.cs
- QilCloneVisitor.cs
- HitTestDrawingContextWalker.cs
- TextUtf8RawTextWriter.cs
- MatrixTransform3D.cs
- xmlsaver.cs
- PeerResolverBindingElement.cs
- ComponentCache.cs
- XmlSerializationWriter.cs
- InvalidMessageContractException.cs
- StorageRoot.cs
- Adorner.cs
- DocumentGridContextMenu.cs
- DescendantOverDescendantQuery.cs
- GetWinFXPath.cs
- Avt.cs
- ZoomingMessageFilter.cs
- UnicastIPAddressInformationCollection.cs
- ObjectContext.cs
- DataRecordObjectView.cs
- SqlDataSourceConfigureSelectPanel.cs
- EditorZone.cs
- WhereQueryOperator.cs
- Mouse.cs
- AuthenticationModuleElement.cs
- ItemsChangedEventArgs.cs
- RsaSecurityTokenAuthenticator.cs
- WindowsScrollBar.cs
- AudioLevelUpdatedEventArgs.cs
- ContentPosition.cs
- LogReservationCollection.cs
- LinqExpressionNormalizer.cs
- DataBoundControl.cs
- BamlCollectionHolder.cs
- MailDefinition.cs
- ExpressionBuilderContext.cs
- ArcSegment.cs
- PropertyGridCommands.cs
- WindowsAuthenticationEventArgs.cs
- SapiAttributeParser.cs
- HelpKeywordAttribute.cs
- ObjectRef.cs
- StateWorkerRequest.cs
- AppSecurityManager.cs
- OptimizedTemplateContentHelper.cs
- GridViewSelectEventArgs.cs
- DbDeleteCommandTree.cs
- newitemfactory.cs
- WindowsGraphics.cs
- cookie.cs
- TreeNodeBinding.cs
- SelectionBorderGlyph.cs
- ObjectStateEntryBaseUpdatableDataRecord.cs