2019-12-04 17:10:06 +01:00
using Unosquare.Swan.Formatters ;
using System ;
using System.IO ;
using System.Linq ;
using System.Security.Cryptography ;
using System.Text ;
using System.Text.RegularExpressions ;
namespace Unosquare.Swan {
/// <summary>
/// String related extension methods.
/// </summary>
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 < MD5 > Md5Hasher = new Lazy < MD5 > ( MD5 . Create , true ) ;
private static readonly Lazy < SHA1 > SHA1Hasher = new Lazy < SHA1 > ( SHA1 . Create , true ) ;
private static readonly Lazy < SHA256 > SHA256Hasher = new Lazy < SHA256 > ( SHA256 . Create , true ) ;
private static readonly Lazy < SHA512 > SHA512Hasher = new Lazy < SHA512 > ( SHA512 . Create , true ) ;
private static readonly Lazy < Regex > SplitLinesRegex =
new Lazy < Regex > (
( ) = > new Regex ( "\r\n|\r|\n" , StandardRegexOptions ) ) ;
private static readonly Lazy < Regex > UnderscoreRegex =
new Lazy < Regex > (
( ) = > new Regex ( @"_" , StandardRegexOptions ) ) ;
private static readonly Lazy < Regex > CamelCaseRegEx =
new Lazy < Regex > (
( ) = >
new Regex ( @"[a-z][A-Z]" ,
StandardRegexOptions ) ) ;
private static readonly Lazy < MatchEvaluator > SplitCamelCaseString = new Lazy < MatchEvaluator > ( ( ) = > m = > {
String x = m . ToString ( ) ;
return x [ 0 ] + " " + x . Substring ( 1 , x . Length - 1 ) ;
} ) ;
private static readonly Lazy < String [ ] > InvalidFilenameChars =
new Lazy < String [ ] > ( ( ) = > Path . GetInvalidFileNameChars ( ) . Select ( c = > c . ToString ( ) ) . ToArray ( ) ) ;
#endregion
2019-02-17 14:08:57 +01:00
/// <summary>
2019-12-04 17:10:06 +01:00
/// Computes the MD5 hash of the given stream.
/// Do not use for large streams as this reads ALL bytes at once.
2019-02-17 14:08:57 +01:00
/// </summary>
2019-12-04 17:10:06 +01:00
/// <param name="stream">The stream.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>
/// The computed hash code.
/// </returns>
/// <exception cref="ArgumentNullException">stream.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
public static Byte [ ] ComputeMD5 ( this Stream stream , Boolean createHasher = false ) {
if ( stream = = null ) {
throw new ArgumentNullException ( nameof ( stream ) ) ;
}
2019-02-17 14:08:57 +01:00
#if NET452
2019-12-04 17:10:06 +01:00
MD5 md5 = MD5 . Create ( ) ;
const Int32 bufferSize = 4096 ;
Byte [ ] readAheadBuffer = new Byte [ bufferSize ] ;
Int32 readAheadBytesRead = stream . Read ( readAheadBuffer , 0 , readAheadBuffer . Length ) ;
do {
Int32 bytesRead = readAheadBytesRead ;
Byte [ ] 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 ;
2019-02-17 14:08:57 +01:00
#else
using ( var ms = new MemoryStream ( ) )
{
stream . Position = 0 ;
stream . CopyTo ( ms ) ;
return ( createHasher ? MD5 . Create ( ) : Md5Hasher . Value ) . ComputeHash ( ms . ToArray ( ) ) ;
}
#endif
2019-12-04 17:10:06 +01:00
}
/// <summary>
/// Computes the MD5 hash of the given string using UTF8 byte encoding.
/// </summary>
/// <param name="value">The input string.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>The computed hash code.</returns>
public static Byte [ ] ComputeMD5 ( this String value , Boolean createHasher = false ) = >
Encoding . UTF8 . GetBytes ( value ) . ComputeMD5 ( createHasher ) ;
/// <summary>
/// Computes the MD5 hash of the given byte array.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>The computed hash code.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
public static Byte [ ] ComputeMD5 ( this Byte [ ] data , Boolean createHasher = false ) = >
( createHasher ? MD5 . Create ( ) : Md5Hasher . Value ) . ComputeHash ( data ) ;
/// <summary>
/// Computes the SHA-1 hash of the given string using UTF8 byte encoding.
/// </summary>
/// <param name="value">The input string.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>
/// The computes a Hash-based Message Authentication Code (HMAC)
/// using the SHA1 hash function.
/// </returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
public static Byte [ ] ComputeSha1 ( this String value , Boolean createHasher = false ) {
Byte [ ] inputBytes = Encoding . UTF8 . GetBytes ( value ) ;
return ( createHasher ? SHA1 . Create ( ) : SHA1Hasher . Value ) . ComputeHash ( inputBytes ) ;
}
/// <summary>
/// Computes the SHA-256 hash of the given string using UTF8 byte encoding.
/// </summary>
/// <param name="value">The input string.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>
/// The computes a Hash-based Message Authentication Code (HMAC)
/// by using the SHA256 hash function.
/// </returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
public static Byte [ ] ComputeSha256 ( this String value , Boolean createHasher = false ) {
Byte [ ] inputBytes = Encoding . UTF8 . GetBytes ( value ) ;
return ( createHasher ? SHA256 . Create ( ) : SHA256Hasher . Value ) . ComputeHash ( inputBytes ) ;
}
/// <summary>
/// Computes the SHA-512 hash of the given string using UTF8 byte encoding.
/// </summary>
/// <param name="value">The input string.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>
/// The computes a Hash-based Message Authentication Code (HMAC)
/// using the SHA512 hash function.
/// </returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
public static Byte [ ] ComputeSha512 ( this String value , Boolean createHasher = false ) {
Byte [ ] inputBytes = Encoding . UTF8 . GetBytes ( value ) ;
return ( createHasher ? SHA512 . Create ( ) : SHA512Hasher . Value ) . ComputeHash ( inputBytes ) ;
}
/// <summary>
/// Returns a string that represents the given item
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
/// overload exists.
/// </summary>
/// <param name="obj">The item.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String ToStringInvariant ( this Object obj ) {
if ( obj = = null ) {
return String . Empty ;
}
Type itemType = obj . GetType ( ) ;
return itemType = = typeof ( String )
? obj as String
: Definitions . BasicTypesInfo . ContainsKey ( itemType )
2019-02-17 14:08:57 +01:00
? Definitions . BasicTypesInfo [ itemType ] . ToStringInvariant ( obj )
2019-12-04 17:10:06 +01:00
: obj . ToString ( ) ;
}
/// <summary>
/// Returns a string that represents the given item
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
/// overload exists.
/// </summary>
/// <typeparam name="T">The type to get the string.</typeparam>
/// <param name="item">The item.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String ToStringInvariant < T > ( this T item ) = > typeof ( String ) = = typeof ( T ) ? Equals ( item , default ( T ) ) ? String . Empty : item as String : ToStringInvariant ( item as Object ) ;
/// <summary>
/// Removes the control characters from a string except for those specified.
/// </summary>
/// <param name="value">The input.</param>
/// <param name="excludeChars">When specified, these characters will not be removed.</param>
/// <returns>
/// A string that represents the current object.
/// </returns>
/// <exception cref="ArgumentNullException">input.</exception>
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 ( ) ) ;
}
/// <summary>
/// Removes all control characters from a string, including new line sequences.
/// </summary>
/// <param name="value">The input.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
/// <exception cref="ArgumentNullException">input.</exception>
public static String RemoveControlChars ( this String value ) = > value . RemoveControlCharsExcept ( null ) ;
/// <summary>
/// Outputs JSON string representing this object.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="format">if set to <c>true</c> format the output.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String ToJson ( this Object obj , Boolean format = true ) = >
obj = = null ? String . Empty : Json . Serialize ( obj , format ) ;
/// <summary>
/// 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.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String Stringify ( this Object obj ) {
if ( obj = = null ) {
return "(null)" ;
}
try {
String jsonText = Json . Serialize ( obj , false , "$type" ) ;
Object jsonData = Json . Deserialize ( jsonText ) ;
return new HumanizeJson ( jsonData , 0 ) . GetResult ( ) ;
} catch {
return obj . ToStringInvariant ( ) ;
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="value">The string.</param>
/// <param name="startIndex">The start index.</param>
/// <param name="endIndex">The end index.</param>
/// <returns>Retrieves a substring from this instance.</returns>
public static String Slice ( this String value , Int32 startIndex , Int32 endIndex ) {
if ( value = = null ) {
return String . Empty ;
}
Int32 end = endIndex . Clamp ( startIndex , value . Length - 1 ) ;
return startIndex > = end ? String . Empty : value . Substring ( startIndex , end - startIndex + 1 ) ;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="startIndex">The start index.</param>
/// <param name="length">The length.</param>
/// <returns>Retrieves a substring from this instance.</returns>
public static String SliceLength ( this String str , Int32 startIndex , Int32 length ) {
if ( str = = null ) {
return String . Empty ;
}
Int32 start = startIndex . Clamp ( 0 , str . Length - 1 ) ;
Int32 len = length . Clamp ( 0 , str . Length - start ) ;
return len = = 0 ? String . Empty : str . Substring ( start , len ) ;
}
/// <summary>
/// Splits the specified text into r, n or rn separated lines.
/// </summary>
/// <param name="value">The text.</param>
/// <returns>
/// An array whose elements contain the substrings from this instance
/// that are delimited by one or more characters in separator.
/// </returns>
public static String [ ] ToLines ( this String value ) = >
value = = null ? new String [ ] { } : SplitLinesRegex . Value . Split ( value ) ;
/// <summary>
/// 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.
/// </summary>
/// <param name="value">The identifier-style string.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String Humanize ( this String value ) {
if ( value = = null ) {
return String . Empty ;
}
String returnValue = UnderscoreRegex . Value . Replace ( value , " " ) ;
returnValue = CamelCaseRegEx . Value . Replace ( returnValue , SplitCamelCaseString . Value ) ;
return returnValue ;
}
/// <summary>
/// Indents the specified multi-line text with the given amount of leading spaces
/// per line.
/// </summary>
/// <param name="value">The text.</param>
/// <param name="spaces">The spaces.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String Indent ( this String value , Int32 spaces = 4 ) {
if ( value = = null ) {
value = String . Empty ;
}
if ( spaces < = 0 ) {
return value ;
}
String [ ] lines = value . ToLines ( ) ;
StringBuilder builder = new StringBuilder ( ) ;
String indentStr = new String ( ' ' , spaces ) ;
foreach ( String line in lines ) {
_ = builder . AppendLine ( $"{indentStr}{line}" ) ;
}
return builder . ToString ( ) . TrimEnd ( ) ;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="value">The string.</param>
/// <param name="charIndex">Index of the character.</param>
/// <returns>A 2-tuple whose value is (item1, item2).</returns>
public static Tuple < Int32 , Int32 > TextPositionAt ( this String value , Int32 charIndex ) {
if ( value = = null ) {
return Tuple . Create ( 0 , 0 ) ;
}
Int32 index = charIndex . Clamp ( 0 , value . Length - 1 ) ;
Int32 lineIndex = 0 ;
Int32 colNumber = 0 ;
for ( Int32 i = 0 ; i < = index ; i + + ) {
if ( value [ i ] = = '\n' ) {
lineIndex + + ;
colNumber = 0 ;
continue ;
}
if ( value [ i ] ! = '\r' ) {
colNumber + + ;
}
}
return Tuple . Create ( lineIndex + 1 , colNumber ) ;
}
/// <summary>
/// Makes the file name system safe.
/// </summary>
/// <param name="value">The s.</param>
/// <returns>
/// A string with a safe file name.
/// </returns>
/// <exception cref="ArgumentNullException">s.</exception>
public static String ToSafeFilename ( this String value ) = > value = = null
? throw new ArgumentNullException ( nameof ( value ) )
: InvalidFilenameChars . Value
. Aggregate ( value , ( current , c ) = > current . Replace ( c , String . Empty ) )
. Slice ( 0 , 220 ) ;
/// <summary>
/// Formats a long into the closest bytes string.
/// </summary>
/// <param name="bytes">The bytes length.</param>
/// <returns>
/// The string representation of the current Byte object, formatted as specified by the format parameter.
/// </returns>
public static String FormatBytes ( this Int64 bytes ) = > ( ( UInt64 ) bytes ) . FormatBytes ( ) ;
/// <summary>
/// Formats a long into the closest bytes string.
/// </summary>
/// <param name="bytes">The bytes length.</param>
/// <returns>
/// A copy of format in which the format items have been replaced by the string
/// representations of the corresponding arguments.
/// </returns>
public static String FormatBytes ( this UInt64 bytes ) {
Int32 i ;
Double dblSByte = bytes ;
for ( i = 0 ; i < ByteSuffixes . Length & & bytes > = 1024 ; i + + , bytes / = 1024 ) {
dblSByte = bytes / 1024.0 ;
}
return $"{dblSByte:0.##} {ByteSuffixes[i]}" ;
}
/// <summary>
/// Truncates the specified value.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="maximumLength">The maximum length.</param>
/// <returns>
/// Retrieves a substring from this instance.
/// The substring starts at a specified character position and has a specified length.
/// </returns>
public static String Truncate ( this String value , Int32 maximumLength ) = >
Truncate ( value , maximumLength , String . Empty ) ;
/// <summary>
/// Truncates the specified value and append the omission last.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="maximumLength">The maximum length.</param>
/// <param name="omission">The omission.</param>
/// <returns>
/// Retrieves a substring from this instance.
/// The substring starts at a specified character position and has a specified length.
/// </returns>
public static String Truncate ( this String value , Int32 maximumLength , String omission ) = > value = = null
? null
: value . Length > maximumLength
? value . Substring ( 0 , maximumLength ) + ( omission ? ? String . Empty )
: value ;
/// <summary>
/// Determines whether the specified <see cref="String"/> contains any of characters in
/// the specified array of <see cref="Char"/>.
/// </summary>
/// <returns>
/// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>;
/// otherwise, <c>false</c>.
/// </returns>
/// <param name="value">
/// A <see cref="String"/> to test.
/// </param>
/// <param name="chars">
/// An array of <see cref="Char"/> that contains characters to find.
/// </param>
public static Boolean Contains ( this String value , params Char [ ] chars ) = >
chars ? . Length = = 0 | | ! String . IsNullOrEmpty ( value ) & & value . IndexOfAny ( chars ) > - 1 ;
/// <summary>
/// Replaces all chars in a string.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="replaceValue">The replace value.</param>
/// <param name="chars">The chars.</param>
/// <returns>The string with the characters replaced.</returns>
public static String ReplaceAll ( this String value , String replaceValue , params Char [ ] chars ) = >
chars . Aggregate ( value , ( current , c ) = > current . Replace ( new String ( new [ ] { c } ) , replaceValue ) ) ;
/// <summary>
/// Convert hex character to an integer. Return -1 if char is something
/// other than a hex char.
/// </summary>
/// <param name="value">The c.</param>
/// <returns>Converted integer.</returns>
public static Int32 Hex2Int ( this Char value ) = > value > = '0' & & value < = '9'
? value - '0'
: value > = 'A' & & value < = 'F'
? value - 'A' + 10
: value > = 'a' & & value < = 'f'
? value - 'a' + 10
: - 1 ;
}
2019-02-17 14:08:57 +01:00
}