/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Globalization; using System.Text; namespace QuantConnect { /// /// Provides extension methods for properly parsing and serializing values while properly using /// an IFormatProvider/CultureInfo when applicable /// public static class StringExtensions { private static readonly CultureInfo CultureInfo = CultureInfo.InvariantCulture; private static readonly IFormatProvider FormatProvider = CultureInfo; private static readonly StringComparison StringComparison = StringComparison.InvariantCulture; /// /// Converts the provided as /// using /// public static T ConvertInvariant(this object value) { return (T) value.ConvertInvariant(typeof(T)); } /// /// Converts the provided as /// using /// /// /// This implementation uses the Convert.ToXXX methods. This causes null values to be converted to the default value /// for the provided . This is in contrast to directly calling /// which results in an or a . Since existing code is /// dependent on this null -> default value conversion behavior, it has been preserved in this method. /// public static object ConvertInvariant(this object value, Type conversionType) { switch (Type.GetTypeCode(conversionType)) { // these cases are purposefully ordered to ensure the compiler can generate a jump table vs a binary tree case TypeCode.Empty: throw new ArgumentException(Messages.StringExtensions.ConvertInvariantCannotConvertTo(TypeCode.Empty)); case TypeCode.Object: var convertible = value as IConvertible; if (convertible != null) { return convertible.ToType(conversionType, FormatProvider); } return Convert.ChangeType(value, conversionType, FormatProvider); case TypeCode.DBNull: throw new ArgumentException(Messages.StringExtensions.ConvertInvariantCannotConvertTo(TypeCode.DBNull)); case TypeCode.Boolean: return Convert.ToBoolean(value, FormatProvider); case TypeCode.Char: return Convert.ToChar(value, FormatProvider); case TypeCode.SByte: return Convert.ToSByte(value, FormatProvider); case TypeCode.Byte: return Convert.ToByte(value, FormatProvider); case TypeCode.Int16: return Convert.ToInt16(value, FormatProvider); case TypeCode.UInt16: return Convert.ToUInt16(value, FormatProvider); case TypeCode.Int32: return Convert.ToInt32(value, FormatProvider); case TypeCode.UInt32: return Convert.ToUInt32(value, FormatProvider); case TypeCode.Int64: return Convert.ToInt64(value, FormatProvider); case TypeCode.UInt64: return Convert.ToUInt64(value, FormatProvider); case TypeCode.Single: return Convert.ToSingle(value, FormatProvider); case TypeCode.Double: return Convert.ToDouble(value, FormatProvider); case TypeCode.Decimal: return Convert.ToDecimal(value, FormatProvider); case TypeCode.DateTime: return Convert.ToDateTime(value, FormatProvider); case TypeCode.String: return Convert.ToString(value, FormatProvider); default: return Convert.ChangeType(value, conversionType, FormatProvider); } } /// /// Non-extension method alias for /// This supports the using static QuantConnect.StringExtensions syntax /// and is aimed at ensuring all formatting is piped through this class instead of /// alternatively piping through directly to /// public static string Invariant(FormattableString formattable) { return FormattableString.Invariant(formattable); } /// /// Converts the provided value to a string using /// public static string ToStringInvariant(this IConvertible convertible) { if (convertible == null) { return string.Empty; } return convertible.ToString(FormatProvider); } /// /// Formats the provided value using the specified and /// /// public static string ToStringInvariant(this IFormattable formattable, string format) { if (formattable == null) { return string.Empty; } // if we have a colon, this implies there's a width parameter in the format it seems this isn't handled // as one would expect. For example, specifying something like $"{value,10:0.00}" would force the string // to be at least 10 characters wide with extra padding in the front, but passing the string '10:0.00' or // ',10:0.00' doesn't work. If we are able to detect a colon in the format and the values preceding the colon, // are numeric, then we know it starts with a width parameter and we can pipe it into a custom-formed // string.format call to get the correct output if (format != null) { var indexOfColon = format.IndexOfInvariant(":"); if (indexOfColon != -1) { int padding; var beforeColon = format.Substring(0, indexOfColon); if (int.TryParse(beforeColon, out padding)) { return string.Format(FormatProvider, $"{{0,{format}}}", formattable); } } } return formattable.ToString(format, FormatProvider); } /// /// Provides a convenience methods for converting a to an invariant ISO-8601 string /// public static string ToIso8601Invariant(this DateTime dateTime) { return dateTime.ToStringInvariant("O"); } /// /// Checks if the string starts with the provided using /// while optionally ignoring case. /// public static bool StartsWithInvariant(this string value, string beginning, bool ignoreCase = false) { return value.StartsWith(beginning, ignoreCase, CultureInfo); } /// /// Checks if the string ends with the provided using /// while optionally ignoring case. /// public static bool EndsWithInvariant(this string value, string ending, bool ignoreCase = false) { return value.EndsWith(ending, ignoreCase, CultureInfo); } /// /// Gets the index of the specified using /// public static int IndexOfInvariant(this string value, char character) { return value.IndexOf(character); } /// /// Gets the index of the specified using /// or when is true /// public static int IndexOfInvariant(this string value, string substring, bool ignoreCase = false) { return value.IndexOf(substring, ignoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison ); } /// /// Gets the index of the specified using /// or when is true /// public static int LastIndexOfInvariant(this string value, string substring, bool ignoreCase = false) { return value.LastIndexOf(substring, ignoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison ); } /// /// Provides a shorthand for avoiding the more verbose ternary equivalent. /// Consider the following: /// /// string.IsNullOrEmpty(str) ? (decimal?)null : Convert.ToDecimal(str, CultureInfo.InvariantCulture) /// /// Can be expressed as: /// /// str.IfNotNullOrEmpty<decimal?>(s => Convert.ToDecimal(str, CultureInfo.InvariantCulture)) /// /// When combined with additional methods from this class, reducing further to a declarative: /// /// str.IfNotNullOrEmpty<decimal?>(s => s.ParseDecimalInvariant()) /// str.IfNotNullOrEmpty<decimal?>(s => s.ConvertInvariant<decimal>()) /// /// /// The string value to check for null or empty /// The default value to use if null or empty /// Function run on non-null string w/ length > 0 public static T IfNotNullOrEmpty(this string value, T defaultValue, Func func) { if (string.IsNullOrEmpty(value)) { return defaultValue; } return func(value); } /// /// Provides a shorthand for avoiding the more verbose ternary equivalent. /// Consider the following: /// /// string.IsNullOrEmpty(str) ? (decimal?)null : Convert.ToDecimal(str, CultureInfo.InvariantCulture) /// /// Can be expressed as: /// /// str.IfNotNullOrEmpty<decimal?>(s => Convert.ToDecimal(str, CultureInfo.InvariantCulture)) /// /// When combined with additional methods from this class, reducing further to a declarative: /// /// str.IfNotNullOrEmpty<decimal?>(s => s.ParseDecimalInvariant()) /// str.IfNotNullOrEmpty<decimal?>(s => s.ConvertInvariant<decimal>()) /// /// /// The string value to check for null or empty /// Function run on non-null string w/ length > 0 public static T IfNotNullOrEmpty(this string value, Func func) { return value.IfNotNullOrEmpty(default(T), func); } /// /// Retrieves a substring from this instance. The substring starts at a specified /// character position and has a specified length. /// /// The zero-based starting character position of a substring in this instance /// The number of characters in the substring public static string SafeSubstring(this string value, int startIndex, int length) { if (string.IsNullOrEmpty(value)) { return value; } if (startIndex > value.Length - 1) { return string.Empty; } if (startIndex < - 1) { startIndex = 0; } return value.Substring(startIndex, Math.Min(length, value.Length - startIndex)); } /// /// Truncates a string to the specified maximum length /// /// The string /// The maximum allowed string /// /// A new string with characters if the original one's length was greater than the maximum allowed length. /// Otherwise, the original string is returned. /// public static string Truncate(this string value, int maxLength) { if (value.Length > maxLength) { return value.Substring(0, maxLength); } return value; } } }