namespace Unosquare.Swan
{
using Formatters;
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
///
/// String related extension methods.
///
public static class StringExtensions
{
#region Private Declarations
private const RegexOptions StandardRegexOptions =
RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant;
private static readonly string[] ByteSuffixes = {"B", "KB", "MB", "GB", "TB"};
private static readonly Lazy Md5Hasher = new Lazy(MD5.Create, true);
private static readonly Lazy SHA1Hasher = new Lazy(SHA1.Create, true);
private static readonly Lazy SHA256Hasher = new Lazy(SHA256.Create, true);
private static readonly Lazy SHA512Hasher = new Lazy(SHA512.Create, true);
private static readonly Lazy SplitLinesRegex =
new Lazy(
() => new Regex("\r\n|\r|\n", StandardRegexOptions));
private static readonly Lazy UnderscoreRegex =
new Lazy(
() => new Regex(@"_", StandardRegexOptions));
private static readonly Lazy CamelCaseRegEx =
new Lazy(
() =>
new Regex(@"[a-z][A-Z]",
StandardRegexOptions));
private static readonly Lazy SplitCamelCaseString = new Lazy(() =>
{
return m =>
{
var x = m.ToString();
return x[0] + " " + x.Substring(1, x.Length - 1);
};
});
private static readonly Lazy InvalidFilenameChars =
new Lazy(() => Path.GetInvalidFileNameChars().Select(c => c.ToString()).ToArray());
#endregion
///
/// Computes the MD5 hash of the given stream.
/// Do not use for large streams as this reads ALL bytes at once.
///
/// The stream.
/// if set to true [create hasher].
///
/// The computed hash code.
///
/// stream.
public static byte[] ComputeMD5(this Stream stream, bool createHasher = false)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
#if NET452
var md5 = MD5.Create();
const int bufferSize = 4096;
var readAheadBuffer = new byte[bufferSize];
var readAheadBytesRead = stream.Read(readAheadBuffer, 0, readAheadBuffer.Length);
do
{
var bytesRead = readAheadBytesRead;
var buffer = readAheadBuffer;
readAheadBuffer = new byte[bufferSize];
readAheadBytesRead = stream.Read(readAheadBuffer, 0, readAheadBuffer.Length);
if (readAheadBytesRead == 0)
md5.TransformFinalBlock(buffer, 0, bytesRead);
else
md5.TransformBlock(buffer, 0, bytesRead, buffer, 0);
}
while (readAheadBytesRead != 0);
return md5.Hash;
#else
using (var ms = new MemoryStream())
{
stream.Position = 0;
stream.CopyTo(ms);
return (createHasher ? MD5.Create() : Md5Hasher.Value).ComputeHash(ms.ToArray());
}
#endif
}
///
/// Computes the MD5 hash of the given string using UTF8 byte encoding.
///
/// The input string.
/// if set to true [create hasher].
/// The computed hash code.
public static byte[] ComputeMD5(this string value, bool createHasher = false) =>
Encoding.UTF8.GetBytes(value).ComputeMD5(createHasher);
///
/// Computes the MD5 hash of the given byte array.
///
/// The data.
/// if set to true [create hasher].
/// The computed hash code.
public static byte[] ComputeMD5(this byte[] data, bool createHasher = false) =>
(createHasher ? MD5.Create() : Md5Hasher.Value).ComputeHash(data);
///
/// Computes the SHA-1 hash of the given string using UTF8 byte encoding.
///
/// The input string.
/// if set to true [create hasher].
///
/// The computes a Hash-based Message Authentication Code (HMAC)
/// using the SHA1 hash function.
///
public static byte[] ComputeSha1(this string value, bool createHasher = false)
{
var inputBytes = Encoding.UTF8.GetBytes(value);
return (createHasher ? SHA1.Create() : SHA1Hasher.Value).ComputeHash(inputBytes);
}
///
/// Computes the SHA-256 hash of the given string using UTF8 byte encoding.
///
/// The input string.
/// if set to true [create hasher].
///
/// The computes a Hash-based Message Authentication Code (HMAC)
/// by using the SHA256 hash function.
///
public static byte[] ComputeSha256(this string value, bool createHasher = false)
{
var inputBytes = Encoding.UTF8.GetBytes(value);
return (createHasher ? SHA256.Create() : SHA256Hasher.Value).ComputeHash(inputBytes);
}
///
/// Computes the SHA-512 hash of the given string using UTF8 byte encoding.
///
/// The input string.
/// if set to true [create hasher].
///
/// The computes a Hash-based Message Authentication Code (HMAC)
/// using the SHA512 hash function.
///
public static byte[] ComputeSha512(this string value, bool createHasher = false)
{
var inputBytes = Encoding.UTF8.GetBytes(value);
return (createHasher ? SHA512.Create() : SHA512Hasher.Value).ComputeHash(inputBytes);
}
///
/// Returns a string that represents the given item
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
/// overload exists.
///
/// The item.
/// A that represents the current object.
public static string ToStringInvariant(this object obj)
{
if (obj == null)
return string.Empty;
var itemType = obj.GetType();
if (itemType == typeof(string))
return obj as string;
return Definitions.BasicTypesInfo.ContainsKey(itemType)
? Definitions.BasicTypesInfo[itemType].ToStringInvariant(obj)
: obj.ToString();
}
///
/// Returns a string that represents the given item
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
/// overload exists.
///
/// The type to get the string.
/// The item.
/// A that represents the current object.
public static string ToStringInvariant(this T item)
{
if (typeof(string) == typeof(T))
return Equals(item, default(T)) ? string.Empty : item as string;
return ToStringInvariant(item as object);
}
///
/// Removes the control characters from a string except for those specified.
///
/// The input.
/// When specified, these characters will not be removed.
///
/// A string that represents the current object.
///
/// input.
public static string RemoveControlCharsExcept(this string value, params char[] excludeChars)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (excludeChars == null)
excludeChars = new char[] { };
return new string(value
.Where(c => char.IsControl(c) == false || excludeChars.Contains(c))
.ToArray());
}
///
/// Removes all control characters from a string, including new line sequences.
///
/// The input.
/// A that represents the current object.
/// input.
public static string RemoveControlChars(this string value) => value.RemoveControlCharsExcept(null);
///
/// Outputs JSON string representing this object.
///
/// The object.
/// if set to true format the output.
/// A that represents the current object.
public static string ToJson(this object obj, bool format = true) =>
obj == null ? string.Empty : Json.Serialize(obj, format);
///
/// Returns text representing the properties of the specified object in a human-readable format.
/// While this method is fairly expensive computationally speaking, it provides an easy way to
/// examine objects.
///
/// The object.
/// A that represents the current object.
public static string Stringify(this object obj)
{
if (obj == null)
return "(null)";
try
{
var jsonText = Json.Serialize(obj, false, "$type");
var jsonData = Json.Deserialize(jsonText);
return new HumanizeJson(jsonData, 0).GetResult();
}
catch
{
return obj.ToStringInvariant();
}
}
///
/// Retrieves a section of the string, inclusive of both, the start and end indexes.
/// This behavior is unlike JavaScript's Slice behavior where the end index is non-inclusive
/// If the string is null it returns an empty string.
///
/// The string.
/// The start index.
/// The end index.
/// Retrieves a substring from this instance.
public static string Slice(this string value, int startIndex, int endIndex)
{
if (value == null)
return string.Empty;
var end = endIndex.Clamp(startIndex, value.Length - 1);
return startIndex >= end ? string.Empty : value.Substring(startIndex, (end - startIndex) + 1);
}
///
/// Gets a part of the string clamping the length and startIndex parameters to safe values.
/// If the string is null it returns an empty string. This is basically just a safe version
/// of string.Substring.
///
/// The string.
/// The start index.
/// The length.
/// Retrieves a substring from this instance.
public static string SliceLength(this string str, int startIndex, int length)
{
if (str == null)
return string.Empty;
var start = startIndex.Clamp(0, str.Length - 1);
var len = length.Clamp(0, str.Length - start);
return len == 0 ? string.Empty : str.Substring(start, len);
}
///
/// Splits the specified text into r, n or rn separated lines.
///
/// The text.
///
/// An array whose elements contain the substrings from this instance
/// that are delimited by one or more characters in separator.
///
public static string[] ToLines(this string value) =>
value == null ? new string[] { } : SplitLinesRegex.Value.Split(value);
///
/// Humanizes (make more human-readable) an identifier-style string
/// in either camel case or snake case. For example, CamelCase will be converted to
/// Camel Case and Snake_Case will be converted to Snake Case.
///
/// The identifier-style string.
/// A that represents the current object.
public static string Humanize(this string value)
{
if (value == null)
return string.Empty;
var returnValue = UnderscoreRegex.Value.Replace(value, " ");
returnValue = CamelCaseRegEx.Value.Replace(returnValue, SplitCamelCaseString.Value);
return returnValue;
}
///
/// Indents the specified multi-line text with the given amount of leading spaces
/// per line.
///
/// The text.
/// The spaces.
/// A that represents the current object.
public static string Indent(this string value, int spaces = 4)
{
if (value == null) value = string.Empty;
if (spaces <= 0) return value;
var lines = value.ToLines();
var builder = new StringBuilder();
var indentStr = new string(' ', spaces);
foreach (var line in lines)
{
builder.AppendLine($"{indentStr}{line}");
}
return builder.ToString().TrimEnd();
}
///
/// Gets the line and column number (i.e. not index) of the
/// specified character index. Useful to locate text in a multi-line
/// string the same way a text editor does.
/// Please not that the tuple contains first the line number and then the
/// column number.
///
/// The string.
/// Index of the character.
/// A 2-tuple whose value is (item1, item2).
public static Tuple TextPositionAt(this string value, int charIndex)
{
if (value == null)
return Tuple.Create(0, 0);
var index = charIndex.Clamp(0, value.Length - 1);
var lineIndex = 0;
var colNumber = 0;
for (var i = 0; i <= index; i++)
{
if (value[i] == '\n')
{
lineIndex++;
colNumber = 0;
continue;
}
if (value[i] != '\r')
colNumber++;
}
return Tuple.Create(lineIndex + 1, colNumber);
}
///
/// Makes the file name system safe.
///
/// The s.
///
/// A string with a safe file name.
///
/// s.
public static string ToSafeFilename(this string value)
{
return value == null
? throw new ArgumentNullException(nameof(value))
: InvalidFilenameChars.Value
.Aggregate(value, (current, c) => current.Replace(c, string.Empty))
.Slice(0, 220);
}
///
/// Formats a long into the closest bytes string.
///
/// The bytes length.
///
/// The string representation of the current Byte object, formatted as specified by the format parameter.
///
public static string FormatBytes(this long bytes) => ((ulong) bytes).FormatBytes();
///
/// Formats a long into the closest bytes string.
///
/// The bytes length.
///
/// A copy of format in which the format items have been replaced by the string
/// representations of the corresponding arguments.
///
public static string FormatBytes(this ulong bytes)
{
int i;
double dblSByte = bytes;
for (i = 0; i < ByteSuffixes.Length && bytes >= 1024; i++, bytes /= 1024)
{
dblSByte = bytes / 1024.0;
}
return $"{dblSByte:0.##} {ByteSuffixes[i]}";
}
///
/// Truncates the specified value.
///
/// The value.
/// The maximum length.
///
/// Retrieves a substring from this instance.
/// The substring starts at a specified character position and has a specified length.
///
public static string Truncate(this string value, int maximumLength) =>
Truncate(value, maximumLength, string.Empty);
///
/// Truncates the specified value and append the omission last.
///
/// The value.
/// The maximum length.
/// The omission.
///
/// Retrieves a substring from this instance.
/// The substring starts at a specified character position and has a specified length.
///
public static string Truncate(this string value, int maximumLength, string omission)
{
if (value == null)
return null;
return value.Length > maximumLength
? value.Substring(0, maximumLength) + (omission ?? string.Empty)
: value;
}
///
/// Determines whether the specified contains any of characters in
/// the specified array of .
///
///
/// true if contains any of ;
/// otherwise, false.
///
///
/// A to test.
///
///
/// An array of that contains characters to find.
///
public static bool Contains(this string value, params char[] chars) =>
chars?.Length == 0 || (!string.IsNullOrEmpty(value) && value.IndexOfAny(chars) > -1);
///
/// Replaces all chars in a string.
///
/// The value.
/// The replace value.
/// The chars.
/// The string with the characters replaced.
public static string ReplaceAll(this string value, string replaceValue, params char[] chars) =>
chars.Aggregate(value, (current, c) => current.Replace(new string(new[] {c}), replaceValue));
///
/// Convert hex character to an integer. Return -1 if char is something
/// other than a hex char.
///
/// The c.
/// Converted integer.
public static int Hex2Int(this char value)
{
return value >= '0' && value <= '9'
? value - '0'
: value >= 'A' && value <= 'F'
? value - 'A' + 10
: value >= 'a' && value <= 'f'
? value - 'a' + 10
: -1;
}
}
}