using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; namespace API.Extensions; #nullable enable public static partial class StringExtensions { private static readonly Regex SentenceCaseRegex = new(@"(^[a-z])|\.\s+(.)", RegexOptions.ExplicitCapture | RegexOptions.Compiled, Services.Tasks.Scanner.Parser.Parser.RegexTimeout); public static string Sanitize(this string input) { if (string.IsNullOrEmpty(input)) return string.Empty; // Remove all newline and control characters var sanitized = input .Replace(Environment.NewLine, string.Empty) .Replace("\n", string.Empty) .Replace("\r", string.Empty); // Optionally remove other potentially unwanted characters sanitized = Regex.Replace(sanitized, @"[^\u0020-\u007E]", string.Empty); // Removes non-printable ASCII return sanitized.Trim(); // Trim any leading/trailing whitespace } public static string SentenceCase(this string value) { return SentenceCaseRegex.Replace(value.ToLower(), s => s.Value.ToUpper()); } /// /// Apply normalization on the String /// /// /// public static string ToNormalized(this string? value) { return string.IsNullOrEmpty(value) ? string.Empty : Services.Tasks.Scanner.Parser.Parser.Normalize(value); } public static float AsFloat(this string? value, float defaultValue = 0.0f) { return string.IsNullOrEmpty(value) ? defaultValue : float.Parse(value, CultureInfo.InvariantCulture); } public static double AsDouble(this string? value, double defaultValue = 0.0f) { return string.IsNullOrEmpty(value) ? defaultValue : double.Parse(value, CultureInfo.InvariantCulture); } public static string TrimPrefix(this string? value, string prefix) { if (string.IsNullOrEmpty(value)) return string.Empty; if (!value.StartsWith(prefix)) return value; return value.Substring(prefix.Length); } /// /// Censor the input string by removing all but the first and last char. /// /// /// /// If the input is an email (contains @), the domain will remain untouched public static string Censor(this string? input) { if (string.IsNullOrWhiteSpace(input)) return input ?? string.Empty; var atIdx = input.IndexOf('@'); if (atIdx == -1) { return $"{input[0]}{new string('*', input.Length - 1)}"; } return input[0] + new string('*', atIdx - 1) + input[atIdx..]; } /// /// Repeat returns a string that is equal to the original string repeat n times /// /// String to repeat /// Amount of times to repeat /// public static string Repeat(this string? input, int n) { return string.IsNullOrEmpty(input) ? string.Empty : string.Concat(Enumerable.Repeat(input, n)); } public static IList ParseIntArray(this string value) { if (string.IsNullOrWhiteSpace(value)) { return []; } return value.Split(',') .Where(s => !string.IsNullOrEmpty(s)) .Select(int.Parse) .ToList(); } /// /// Parses a human-readable file size string (e.g. "1.43 GB") into bytes. /// /// The input string like "1.43 GB", "4.2 KB", "512 B" /// Byte count as long public static long ParseHumanReadableBytes(this string input) { if (string.IsNullOrWhiteSpace(input)) throw new ArgumentException("Input cannot be null or empty.", nameof(input)); var match = HumanReadableBytesRegex().Match(input); if (!match.Success) throw new FormatException($"Invalid format: '{input}'"); var value = double.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); var unit = match.Groups[2].Value.ToUpperInvariant(); var multiplier = unit switch { "B" => 1L, "KB" => 1L << 10, "MB" => 1L << 20, "GB" => 1L << 30, "TB" => 1L << 40, "PB" => 1L << 50, "EB" => 1L << 60, _ => throw new FormatException($"Unknown unit: '{unit}'") }; return (long)(value * multiplier); } [GeneratedRegex(@"^\s*(\d+(?:\.\d+)?)\s*([KMGTPE]?B)\s*$", RegexOptions.IgnoreCase)] private static partial Regex HumanReadableBytesRegex(); }