/*
* 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;
}
}
}