208 lines
7.5 KiB
C#
208 lines
7.5 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
|
|
using Swan.Formatters;
|
|
|
|
namespace 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 Lazy<Regex> SplitLinesRegex = new Lazy<Regex>(() => new Regex("\r\n|\r|\n", StandardRegexOptions));
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Returns a string that represents the given item
|
|
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
|
|
/// overload exists.
|
|
/// </summary>
|
|
/// <param name="this">The item.</param>
|
|
/// <returns>A <see cref="String" /> that represents the current object.</returns>
|
|
public static String ToStringInvariant(this Object? @this) {
|
|
if(@this == null) {
|
|
return String.Empty;
|
|
}
|
|
|
|
Type itemType = @this.GetType();
|
|
|
|
return itemType == typeof(String) ? @this as String ?? String.Empty : Definitions.BasicTypesInfo.Value.ContainsKey(itemType) ? Definitions.BasicTypesInfo.Value[itemType].ToStringInvariant(@this) : @this.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="String" /> that represents the current object.</returns>
|
|
public static String ToStringInvariant<T>(this T item) => typeof(String) == typeof(T) ? item as String ?? String.Empty : 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>
|
|
// [Obsolete("NEED", false)]
|
|
public static String RemoveControlCharsExcept(this String value, params Char[]? excludeChars) {
|
|
if(value == null) {
|
|
throw new ArgumentNullException(nameof(value));
|
|
}
|
|
|
|
if(excludeChars == null) {
|
|
excludeChars = Array.Empty<Char>();
|
|
}
|
|
|
|
return new String(value.Where(c => Char.IsControl(c) == false || excludeChars.Contains(c)).ToArray());
|
|
}
|
|
|
|
/// <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="this">The object.</param>
|
|
/// <returns>A <see cref="String" /> that represents the current object.</returns>
|
|
// [Obsolete("NEED", false)]
|
|
public static String Stringify(this Object @this) {
|
|
if(@this == null) {
|
|
return "(null)";
|
|
}
|
|
|
|
try {
|
|
String jsonText = Json.Serialize(@this, false, "$type");
|
|
Object? jsonData = Json.Deserialize(jsonText);
|
|
|
|
return new HumanizeJson(jsonData, 0).GetResult();
|
|
} catch {
|
|
return @this.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="this">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 @this, Int32 startIndex, Int32 endIndex) {
|
|
if(@this == null) {
|
|
return String.Empty;
|
|
}
|
|
|
|
Int32 end = endIndex.Clamp(startIndex, @this.Length - 1);
|
|
|
|
return startIndex >= end ? String.Empty : @this.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="this">The string.</param>
|
|
/// <param name="startIndex">The start index.</param>
|
|
/// <param name="length">The length.</param>
|
|
/// <returns>Retrieves a substring from this instance.</returns>
|
|
// [Obsolete("NEED", false)]
|
|
public static String SliceLength(this String @this, Int32 startIndex, Int32 length) {
|
|
if(@this == null) {
|
|
return String.Empty;
|
|
}
|
|
|
|
Int32 start = startIndex.Clamp(0, @this.Length - 1);
|
|
Int32 len = length.Clamp(0, @this.Length - start);
|
|
|
|
return len == 0 ? String.Empty : @this.Substring(start, len);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Splits the specified text into r, n or rn separated lines.
|
|
/// </summary>
|
|
/// <param name="this">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 @this) => @this == null ? Array.Empty<String>() : SplitLinesRegex.Value.Split(@this);
|
|
|
|
/// <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="String" /> that represents the current object.</returns>
|
|
// [Obsolete("NEED", false)]
|
|
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);
|
|
}
|
|
}
|
|
}
|