diff options
Diffstat (limited to 'src/mscorlib/src/System/Globalization/TextInfo.cs')
-rw-r--r-- | src/mscorlib/src/System/Globalization/TextInfo.cs | 860 |
1 files changed, 389 insertions, 471 deletions
diff --git a/src/mscorlib/src/System/Globalization/TextInfo.cs b/src/mscorlib/src/System/Globalization/TextInfo.cs index 9ece275dd3..fecd2b2970 100644 --- a/src/mscorlib/src/System/Globalization/TextInfo.cs +++ b/src/mscorlib/src/System/Globalization/TextInfo.cs @@ -12,27 +12,19 @@ // //////////////////////////////////////////////////////////////////////////// -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.Diagnostics; - using System.Diagnostics.Contracts; - +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Runtime.Serialization; +using System.Text; +namespace System.Globalization +{ [Serializable] public partial class TextInfo : ICloneable, IDeserializationCallback { - //--------------------------------------------------------------------// - // Internal Information // - //--------------------------------------------------------------------// + ////--------------------------------------------------------------------// + //// Internal Information // + ////--------------------------------------------------------------------// private enum Tristate : byte { @@ -41,49 +33,42 @@ namespace System.Globalization { False, } - // - // Variables. - // + //// + //// Variables. + //// [OptionalField(VersionAdded = 2)] - private String m_listSeparator; - + private String _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. - // + private bool _isReadOnly = false; + + //// _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. + //// _cultureData is the data that backs this class. + //// _textInfoName is the actual name of the textInfo (from cultureData.STEXTINFO) + //// this can be the same as _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 Tristate m_IsAsciiCasingSameAsInvariant = Tristate.NotInitialized; - + private String _cultureName; // Name of the culture that created this text info + [NonSerialized] + private CultureData _cultureData; // Data record for the culture that made us, not for this textinfo + [NonSerialized] + private String _textInfoName; // Name of the text info we're using (ie: _cultureData.STEXTINFO) + [NonSerialized] + private Tristate _isAsciiCasingSameAsInvariant = Tristate.NotInitialized; + + // _invariantMode is defined for the perf reason as accessing the instance field is faster than access the static property GlobalizationMode.Invariant + [NonSerialized] + private readonly bool _invariantMode = GlobalizationMode.Invariant; // Invariant text info - internal static TextInfo Invariant + internal static TextInfo Invariant { - get + get { if (s_Invariant == null) s_Invariant = new TextInfo(CultureData.Invariant); @@ -92,88 +77,31 @@ namespace System.Globalization { } internal volatile static TextInfo s_Invariant; - //////////////////////////////////////////////////////////////////////// - // - // TextInfo Constructors - // - // Implements CultureInfo.TextInfo. - // - //////////////////////////////////////////////////////////////////////// - internal TextInfo(CultureData cultureData) + ////////////////////////////////////////////////////////////////////////// + //// + //// TextInfo Constructors + //// + //// Implements CultureInfo.TextInfo. + //// + ////////////////////////////////////////////////////////////////////////// + internal unsafe 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; + _cultureData = cultureData; + _cultureName = _cultureData.CultureName; + _textInfoName = _cultureData.STEXTINFO; + FinishInitialization(_textInfoName); } - //////////////////////////////////////////////////////////////////////// - // - // 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; - - // 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; - - - [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; - } + [OnSerializing] + private void OnSerializing(StreamingContext ctx) { } - private void OnDeserialized() + [OnDeserializing] + private void OnDeserializing(StreamingContext ctx) { - // 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; - } + // Clear these so we can check if we've fixed them yet + _cultureData = null; + _cultureName = null; } [OnDeserialized] @@ -182,142 +110,56 @@ namespace System.Globalization { OnDeserialized(); } - [OnSerializing] - private void OnSerializing(StreamingContext ctx) + void IDeserializationCallback.OnDeserialization(Object sender) { - // Relabel our name since Whidbey expects it to be called customCultureName - this.customCultureName = this.m_cultureName; + OnDeserialized(); + } -#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 + private void OnDeserialized() + { + // this method will be called twice because of the support of IDeserializationCallback + if (_cultureData == null) + { + // Get the text info name belonging to that culture + _cultureData = CultureInfo.GetCultureInfo(_cultureName)._cultureData; + _textInfoName = _cultureData.STEXTINFO; + FinishInitialization(_textInfoName); + } + } // // Internal ordinal comparison functions // - internal static int GetHashCodeOrdinalIgnoreCase(String s) - { - return GetHashCodeOrdinalIgnoreCase(s, false, 0); - } - internal static int GetHashCodeOrdinalIgnoreCase(String s, bool forceRandomizedHashing, long additionalEntropy) + internal static int GetHashCodeOrdinalIgnoreCase(String s) { // 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)); - } - - 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. - internal static unsafe int CompareOrdinalIgnoreCase(String str1, String str2) - { - // Compare the whole string and ignore case. - return InternalCompareStringOrdinalIgnoreCase(str1, 0, str2, 0, str1.Length, str2.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 CompareOrdinalIgnoreCaseEx(String strA, int indexA, String strB, int indexB, int lengthA, int lengthB ) - { - Debug.Assert(strA.Length >= indexA + lengthA, "[TextInfo.CompareOrdinalIgnoreCaseEx] Caller should've validated strA.Length >= indexA + lengthA"); - Debug.Assert(strB.Length >= indexB + lengthB, "[TextInfo.CompareOrdinalIgnoreCaseEx] Caller should've validated strB.Length >= indexB + lengthB"); - return InternalCompareStringOrdinalIgnoreCase(strA, indexA, strB, indexB, lengthA, lengthB); + return (Invariant.GetCaseInsensitiveHashCode(s)); } + // Currently we don't have native functions to do this, so we do it the hard way internal static int IndexOfStringOrdinalIgnoreCase(String source, String value, int startIndex, int count) { - Debug.Assert(source != null, "[TextInfo.IndexOfStringOrdinalIgnoreCase] Caller should've validated source != null"); - Debug.Assert(value != null, "[TextInfo.IndexOfStringOrdinalIgnoreCase] Caller should've validated value != null"); - Debug.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) + if (count > source.Length || count < 0 || startIndex < 0 || startIndex >= source.Length || startIndex + count > source.Length) { - return 0; + return -1; } - // fast path - int ret = -1; - if (TryFastFindStringOrdinalIgnoreCase(Microsoft.Win32.Win32Native.FIND_FROMSTART, source, startIndex, value, count, ref ret)) - return ret; - - // 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++) - { - // We should always have the same or more characters left to search than our actual pattern - Debug.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) - { - return startIndex; - } - } - - // Not found - return -1; + return CultureInfo.InvariantCulture.CompareInfo.IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true); } + // Currently we don't have native functions to do this, so we do it the hard way internal static int LastIndexOfStringOrdinalIgnoreCase(String source, String value, int startIndex, int count) { - Debug.Assert(source != null, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated source != null"); - Debug.Assert(value != null, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated value != null"); - Debug.Assert(startIndex - count+1 >= 0, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated startIndex - count+1 >= 0"); - Debug.Assert(startIndex <= source.Length, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated startIndex <= source.Length"); - - // If value is Empty, the return value is startIndex - if (value.Length == 0) - { - return startIndex; - } - - // fast path - int ret = -1; - if (TryFastFindStringOrdinalIgnoreCase(Microsoft.Win32.Win32Native.FIND_FROMEND, source, startIndex, value, count, ref ret)) - return ret; - - // 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) + if (count > source.Length || count < 0 || startIndex < 0 || startIndex > source.Length - 1 || (startIndex - count + 1 < 0)) { - startIndex -= (value.Length - 1); + return -1; } - for (; startIndex >= minIndex; startIndex--) - { - if (CompareOrdinalIgnoreCaseEx(source, startIndex, value, 0, value.Length, value.Length) == 0) - { - return startIndex; - } - } - - // Not found - return -1; + return CultureInfo.InvariantCulture.CompareInfo.LastIndexOfOrdinal(source, value, startIndex, count, ignoreCase: true); } - //////////////////////////////////////////////////////////////////////// // // CodePage @@ -331,72 +173,62 @@ namespace System.Globalization { //////////////////////////////////////////////////////////////////////// - public virtual int ANSICodePage + public virtual int ANSICodePage { - get + get { - return (this.m_cultureData.IDEFAULTANSICODEPAGE); + return (_cultureData.IDEFAULTANSICODEPAGE); } } - - public virtual int OEMCodePage + + public virtual int OEMCodePage { - get + get { - return (this.m_cultureData.IDEFAULTOEMCODEPAGE); + return (_cultureData.IDEFAULTOEMCODEPAGE); } } - public virtual int MacCodePage + public virtual int MacCodePage { - get + get { - return (this.m_cultureData.IDEFAULTMACCODEPAGE); + return (_cultureData.IDEFAULTMACCODEPAGE); } } - public virtual int EBCDICCodePage + public virtual int EBCDICCodePage { - get + get { - return (this.m_cultureData.IDEFAULTEBCDICCODEPAGE); + return (_cultureData.IDEFAULTEBCDICCODEPAGE); } } - //////////////////////////////////////////////////////////////////////// - // - // 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 public int LCID { - get + get { // Just use the LCID from our text info name - return CultureInfo.GetCultureInfo(this.m_textInfoName).LCID; + return CultureInfo.GetCultureInfo(_textInfoName).LCID; } } -#endif - //////////////////////////////////////////////////////////////////////// - // - // CultureName - // - // The name of the culture associated with the current TextInfo. - // - //////////////////////////////////////////////////////////////////////// - public string CultureName + + ////////////////////////////////////////////////////////////////////////// + //// + //// CultureName + //// + //// The name of the culture associated with the current TextInfo. + //// + ////////////////////////////////////////////////////////////////////////// + public string CultureName { - get + get { - return(this.m_textInfoName); + return _textInfoName; } } @@ -407,25 +239,25 @@ namespace System.Globalization { // Detect if the object is readonly. // //////////////////////////////////////////////////////////////////////// - public bool IsReadOnly + public bool IsReadOnly { - get { return (m_isReadOnly); } + get { return (_isReadOnly); } } - //////////////////////////////////////////////////////////////////////// - // - // Clone - // - // Is the implementation of ICloneable. - // - //////////////////////////////////////////////////////////////////////// - public virtual Object Clone() + ////////////////////////////////////////////////////////////////////////// + //// + //// Clone + //// + //// Is the implementation of ICloneable. + //// + ////////////////////////////////////////////////////////////////////////// + public virtual object Clone() { object o = MemberwiseClone(); - ((TextInfo) o).SetReadOnlyState(false); + ((TextInfo)o).SetReadOnlyState(false); return (o); } - + //////////////////////////////////////////////////////////////////////// // // ReadOnly @@ -434,30 +266,29 @@ namespace System.Globalization { // readonly. // //////////////////////////////////////////////////////////////////////// - public static TextInfo ReadOnly(TextInfo textInfo) + public static TextInfo ReadOnly(TextInfo textInfo) { - if (textInfo == null) { throw new ArgumentNullException(nameof(textInfo)); } + if (textInfo == null) { throw new ArgumentNullException(nameof(textInfo)); } Contract.EndContractBlock(); - if (textInfo.IsReadOnly) { return (textInfo); } - + if (textInfo.IsReadOnly) { return (textInfo); } + TextInfo clonedTextInfo = (TextInfo)(textInfo.MemberwiseClone()); clonedTextInfo.SetReadOnlyState(true); - + return (clonedTextInfo); } private void VerifyWritable() { - if (m_isReadOnly) + if (_isReadOnly) { - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly")); + throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); } - Contract.EndContractBlock(); } internal void SetReadOnlyState(bool readOnly) { - m_isReadOnly = readOnly; + _isReadOnly = readOnly; } @@ -468,27 +299,25 @@ namespace System.Globalization { // Returns the string used to separate items in a list. // //////////////////////////////////////////////////////////////////////// - - - public virtual String ListSeparator + public virtual String ListSeparator { - get + get { - if (m_listSeparator == null) { - m_listSeparator = this.m_cultureData.SLIST; + if (_listSeparator == null) + { + _listSeparator = _cultureData.SLIST; } - return (m_listSeparator); + return (_listSeparator); } - set + set { - if (value == null) + if (value == null) { - throw new ArgumentNullException(nameof(value), Environment.GetResourceString("ArgumentNull_String")); + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); } - Contract.EndContractBlock(); VerifyWritable(); - m_listSeparator = value; + _listSeparator = value; } } @@ -500,28 +329,123 @@ namespace System.Globalization { // have different casing semantics from the file systems in Win32. // //////////////////////////////////////////////////////////////////////// - - public unsafe virtual char ToLower(char c) + public unsafe virtual char ToLower(char c) { - if(IsAscii(c) && IsAsciiCasingSameAsInvariant) + if (_invariantMode || (IsAscii(c) && IsAsciiCasingSameAsInvariant)) { return ToLowerAsciiInvariant(c); } - return (InternalChangeCaseChar(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, c, false)); + + return (ChangeCase(c, toUpper: false)); } - public unsafe virtual String ToLower(String str) + public unsafe virtual String ToLower(String str) { if (str == null) { throw new ArgumentNullException(nameof(str)); } - Contract.EndContractBlock(); - return InternalChangeCaseString(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, str, false); + if (_invariantMode) + { + return ToLowerAsciiInvariant(str); + } + return ChangeCase(str, toUpper: false); } - static private Char ToLowerAsciiInvariant(Char c) + private unsafe string ToLowerAsciiInvariant(string s) { - if ('A' <= c && c <= 'Z') + if (s.Length == 0) + { + return string.Empty; + } + + fixed (char* pSource = s) + { + int i = 0; + while (i < s.Length) + { + if ((uint)(pSource[i] - 'A') <= (uint)('Z' - 'A')) + { + break; + } + i++; + } + + if (i >= s.Length) + { + return s; + } + + string result = string.FastAllocateString(s.Length); + fixed (char* pResult = result) + { + for (int j = 0; j < i; j++) + { + pResult[j] = pSource[j]; + } + + pResult[i] = (Char)(pSource[i] | 0x20); + i++; + + while (i < s.Length) + { + pResult[i] = ToLowerAsciiInvariant(pSource[i]); + i++; + } + } + + return result; + } + } + + private unsafe string ToUpperAsciiInvariant(string s) + { + if (s.Length == 0) + { + return string.Empty; + } + + fixed (char* pSource = s) + { + int i = 0; + while (i < s.Length) + { + if ((uint)(pSource[i] - 'a') <= (uint)('z' - 'a')) + { + break; + } + i++; + } + + if (i >= s.Length) + { + return s; + } + + string result = string.FastAllocateString(s.Length); + fixed (char* pResult = result) + { + for (int j = 0; j < i; j++) + { + pResult[j] = pSource[j]; + } + + pResult[i] = (char)(pSource[i] & ~0x20); + i++; + + while (i < s.Length) + { + pResult[i] = ToUpperAsciiInvariant(pSource[i]); + i++; + } + } + + return result; + } + } + + private static Char ToLowerAsciiInvariant(Char c) + { + if ((uint)(c - 'A') <= (uint)('Z' - 'A')) { c = (Char)(c | 0x20); } @@ -536,34 +460,38 @@ namespace System.Globalization { // have different casing semantics from the file systems in Win32. // //////////////////////////////////////////////////////////////////////// - - public unsafe virtual char ToUpper(char c) + public unsafe virtual char ToUpper(char c) { - if (IsAscii(c) && IsAsciiCasingSameAsInvariant) + if (_invariantMode || (IsAscii(c) && IsAsciiCasingSameAsInvariant)) { return ToUpperAsciiInvariant(c); } - return (InternalChangeCaseChar(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, c, true)); + + return (ChangeCase(c, toUpper: true)); } - - public unsafe virtual String ToUpper(String str) + public unsafe virtual String ToUpper(String str) { if (str == null) { throw new ArgumentNullException(nameof(str)); } - Contract.EndContractBlock(); - return InternalChangeCaseString(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, str, true); + + if (_invariantMode) + { + return ToUpperAsciiInvariant(str); + } + + return ChangeCase(str, toUpper: true); } - static private Char ToUpperAsciiInvariant(Char c) + private static Char ToUpperAsciiInvariant(Char c) { - if ('a' <= c && c <= 'z') + if ((uint)(c - 'a') <= (uint)('z' - 'a')) { c = (Char)(c & ~0x20); } return c; } - static private bool IsAscii(Char c) + private static bool IsAscii(Char c) { return c < 0x80; } @@ -572,14 +500,25 @@ namespace System.Globalization { { get { - if (m_IsAsciiCasingSameAsInvariant == Tristate.NotInitialized) + if (_isAsciiCasingSameAsInvariant == Tristate.NotInitialized) { - m_IsAsciiCasingSameAsInvariant = - CultureInfo.GetCultureInfo(m_textInfoName).CompareInfo.Compare("abcdefghijklmnopqrstuvwxyz", + _isAsciiCasingSameAsInvariant = CultureInfo.GetCultureInfo(_textInfoName).CompareInfo.Compare("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", CompareOptions.IgnoreCase) == 0 ? Tristate.True : Tristate.False; } - return m_IsAsciiCasingSameAsInvariant == Tristate.True; + return _isAsciiCasingSameAsInvariant == Tristate.True; + } + } + + // IsRightToLeft + // + // Returns true if the dominant direction of text and UI such as the relative position of buttons and scroll bars + // + public bool IsRightToLeft + { + get + { + return _cultureData.IsRightToLeft; } } @@ -591,21 +530,18 @@ namespace System.Globalization { // 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 @@ -615,14 +551,11 @@ namespace System.Globalization { // and B where A.Equals(B) is true. // //////////////////////////////////////////////////////////////////////// - - public override int GetHashCode() { return (this.CultureName.GetHashCode()); } - //////////////////////////////////////////////////////////////////////// // // ToString @@ -631,14 +564,11 @@ namespace System.Globalization { // TextInfo. // //////////////////////////////////////////////////////////////////////// - - public override String ToString() { - return ("TextInfo - " + this.m_cultureData.CultureName); + return ("TextInfo - " + _cultureData.CultureName); } - // // Titlecasing: // ----------- @@ -653,41 +583,44 @@ namespace System.Globalization { // 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: - // ------------------------------------------------------------------------------------- - // The .NET Framework previously shipped a naive titlecasing implementation. Every word is titlecased - // regardless of language or orthographic practice. Furthermore, apostrophe is always considered to be - // a word joiner as used in English. The longterm vision is to depend on the operating system for - // titlecasing. Windows 7 is expected to be the first release with this feature. On the Macintosh side, - // titlecasing is not available as of version 10.5 of the operating system. - // - public unsafe String ToTitleCase(String str) + public unsafe String ToTitleCase(String str) { - if (str == null) + if (str == null) { throw new ArgumentNullException(nameof(str)); } Contract.EndContractBlock(); - if (str.Length == 0) + if (str.Length == 0) { return (str); } StringBuilder result = new StringBuilder(); - String lowercaseData = null; + string lowercaseData = null; + // Store if the current culture is Dutch (special case) + bool isDutchCulture = CultureName.StartsWith("nl-", StringComparison.OrdinalIgnoreCase); - for (int i = 0; i < str.Length; i++) + for (int i = 0; i < str.Length; i++) { UnicodeCategory charType; int charLen; charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen); - if (Char.CheckLetter(charType)) + if (Char.CheckLetter(charType)) { - // Do the titlecasing for the first character of the word. - i = AddTitlecaseLetter(ref result, ref str, i, charLen) + 1; - + // Special case to check for Dutch specific titlecasing with "IJ" characters + // at the beginning of a word + if (isDutchCulture && i < str.Length - 1 && (str[i] == 'i' || str[i] == 'I') && (str[i+1] == 'j' || str[i+1] == 'J')) + { + result.Append("IJ"); + i += 2; + } + else + { + // 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. @@ -700,42 +633,42 @@ namespace System.Globalization { // bool hasLowerCase = (charType == UnicodeCategory.LowercaseLetter); // Use a loop to find all of the other letters following this letter. - while (i < str.Length) + while (i < str.Length) { charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen); - if (IsLetterCategory(charType)) + if (IsLetterCategory(charType)) { - if (charType == UnicodeCategory.LowercaseLetter) + if (charType == UnicodeCategory.LowercaseLetter) { hasLowerCase = true; } i += charLen; - } - else if (str[i] == '\'') + } + else if (str[i] == '\'') { i++; - if (hasLowerCase) + if (hasLowerCase) { - if (lowercaseData == null) + if (lowercaseData == null) { lowercaseData = this.ToLower(str); } result.Append(lowercaseData, lowercaseStart, i - lowercaseStart); - } - else + } + else { result.Append(str, lowercaseStart, i - lowercaseStart); } lowercaseStart = i; hasLowerCase = true; - } - else if (!IsWordSeparator(charType)) + } + 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 + } + else { // A word separator. Break out of the loop. break; @@ -744,29 +677,29 @@ namespace System.Globalization { int count = i - lowercaseStart; - if (count>0) + if (count > 0) { - if (hasLowerCase) + if (hasLowerCase) { - if (lowercaseData == null) + if (lowercaseData == null) { lowercaseData = this.ToLower(str); } result.Append(lowercaseData, lowercaseStart, count); - } - else + } + else { result.Append(str, lowercaseStart, count); } } - if (i < str.Length) + if (i < str.Length) { // not a letter, just append it i = AddNonLetter(ref result, ref str, i, charLen); } } - else + else { // not a letter, just append it i = AddNonLetter(ref result, ref str, i, charLen); @@ -775,75 +708,73 @@ namespace System.Globalization { return (result.ToString()); } - private static int AddNonLetter(ref StringBuilder result, ref String input, int inputIndex, int charLen) + private static int AddNonLetter(ref StringBuilder result, ref String input, int inputIndex, int charLen) { Debug.Assert(charLen == 1 || charLen == 2, "[TextInfo.AddNonLetter] CharUnicodeInfo.InternalGetUnicodeCategory returned an unexpected charLen!"); - if (charLen == 2) + if (charLen == 2) { // Surrogate pair result.Append(input[inputIndex++]); result.Append(input[inputIndex]); } - else + else { result.Append(input[inputIndex]); - } + } return inputIndex; } - - private int AddTitlecaseLetter(ref StringBuilder result, ref String input, int inputIndex, int charLen) + private int AddTitlecaseLetter(ref StringBuilder result, ref String input, int inputIndex, int charLen) { Debug.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) + if (charLen == 2) { // Surrogate pair - result.Append( this.ToUpper(input.Substring(inputIndex, charLen)) ); + result.Append(ToUpper(input.Substring(inputIndex, charLen))); inputIndex++; } - else + else { - switch (input[inputIndex]) + 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 ); + 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 ); + 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 ); + 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 ); + 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]) ); + result.Append(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 = + private const int c_wordSeparatorMask = /* false */ (0 << 0) | // UppercaseLetter = 0, /* false */ (0 << 1) | // LowercaseLetter = 1, /* false */ (0 << 2) | // TitlecaseLetter = 2, @@ -874,13 +805,13 @@ namespace System.Globalization { /* 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; + return (c_wordSeparatorMask & (1 << (int) category)) != 0; } - private static bool IsLetterCategory(UnicodeCategory uc) + private static bool IsLetterCategory(UnicodeCategory uc) { return (uc == UnicodeCategory.UppercaseLetter || uc == UnicodeCategory.LowercaseLetter @@ -889,76 +820,63 @@ namespace System.Globalization { || uc == UnicodeCategory.OtherLetter); } - // IsRightToLeft // - // Returns true if the dominant direction of text and UI such as the relative position of buttons and scroll bars + // Get case-insensitive hash code for the specified string. // - public bool IsRightToLeft + internal unsafe int GetCaseInsensitiveHashCode(String str) { - get + // Validate inputs + if (str == null) { - return this.m_cultureData.IsRightToLeft; + throw new ArgumentNullException(nameof(str)); } - } - /// <internalonly/> - void IDeserializationCallback.OnDeserialization(Object sender) - { - OnDeserialized(); - } + // This code assumes that ASCII casing is safe for whatever context is passed in. + // this is true today, because we only ever call these methods on Invariant. It would be ideal to refactor + // these methods so they were correct by construction and we could only ever use Invariant. - // - // 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) - { - return GetCaseInsensitiveHashCode(str, false, 0); - } + uint hash = 5381; + uint c; - internal unsafe int GetCaseInsensitiveHashCode(String str, bool forceRandomizedHashing, long additionalEntropy) - { - // Validate inputs - if (str==null) + // Note: We assume that str contains only ASCII characters until + // we hit a non-ASCII character to optimize the common case. + for (int i = 0; i < str.Length; i++) { - throw new ArgumentNullException(nameof(str)); + c = str[i]; + if (c >= 0x80) + { + return GetCaseInsensitiveHashCodeSlow(str); + } + + // If we have a lowercase character, ANDing off 0x20 + // will make it an uppercase character. + if ((c - 'a') <= ('z' - 'a')) + { + c = (uint)((int)c & ~0x20); + } + + hash = ((hash << 5) + hash) ^ c; } - Contract.EndContractBlock(); - // Return our result - return (InternalGetCaseInsHash(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, str, forceRandomizedHashing, additionalEntropy)); + return (int)hash; } - // Change case (ToUpper/ToLower) -- COMNlsInfo::InternalChangeCaseChar - [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 - [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 - [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) - [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 - [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); - } + private unsafe int GetCaseInsensitiveHashCodeSlow(String str) + { + Debug.Assert(str != null); -} + string upper = ToUpper(str); + uint hash = 5381; + uint c; + + for (int i = 0; i < upper.Length; i++) + { + c = upper[i]; + hash = ((hash << 5) + hash) ^ c; + } + return (int)hash; + } + } +} |