// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Net; using System.Text.RegularExpressions; namespace CommunityToolkit.Common; /// /// Helpers for working with strings and string representations. /// public static class StringExtensions { /// /// Regular expression for matching a phone number. /// internal const string PhoneNumberRegex = @"^[+]?(\d{1,3})?[\s.-]?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$"; /// /// Regular expression for matching a string that contains only letters. /// internal const string CharactersRegex = "^[A-Za-z]+$"; /// /// Regular expression for matching an email address. /// /// General Email Regex (RFC 5322 Official Standard) from https://emailregex.com. internal const string EmailRegex = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"; /// /// Regular expression of HTML tags to remove. /// private const string RemoveHtmlTagsRegex = @"(?>(?:[^>'""]+|'[^']*'|""[^""]*"")*)>"; /// /// Regular expression for removing comments from HTML. /// private static readonly Regex RemoveHtmlCommentsRegex = new("", RegexOptions.Singleline); /// /// Regular expression for removing scripts from HTML. /// private static readonly Regex RemoveHtmlScriptsRegex = new(@"(?s)|)", RegexOptions.Singleline | RegexOptions.IgnoreCase); /// /// Regular expression for removing styles from HTML. /// private static readonly Regex RemoveHtmlStylesRegex = new(@"(?s)|)", RegexOptions.Singleline | RegexOptions.IgnoreCase); /// /// Determines whether a string is a valid email address. /// /// The string to test. /// true for a valid email address; otherwise, false. public static bool IsEmail(this string str) => Regex.IsMatch(str, EmailRegex); /// /// Determines whether a string is a valid decimal number. /// /// The string to test. /// true for a valid decimal number; otherwise, false. public static bool IsDecimal([NotNullWhen(true)] this string? str) { return decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out _); } /// /// Determines whether a string is a valid integer. /// /// The string to test. /// true for a valid integer; otherwise, false. public static bool IsNumeric([NotNullWhen(true)] this string? str) { return int.TryParse(str, out _); } /// /// Determines whether a string is a valid phone number. /// /// The string to test. /// true for a valid phone number; otherwise, false. public static bool IsPhoneNumber(this string str) => Regex.IsMatch(str, PhoneNumberRegex); /// /// Determines whether a string contains only letters. /// /// The string to test. /// true if the string contains only letters; otherwise, false. public static bool IsCharacterString(this string str) => Regex.IsMatch(str, CharactersRegex); /// /// Returns a string with HTML comments, scripts, styles, and tags removed. /// /// HTML string. /// Decoded HTML string. [return: NotNullIfNotNull("htmlText")] public static string? DecodeHtml(this string? htmlText) { if (htmlText is null) { return null; } string? ret = htmlText.FixHtml(); // Remove html tags ret = new Regex(RemoveHtmlTagsRegex).Replace(ret, string.Empty); return WebUtility.HtmlDecode(ret); } /// /// Returns a string with HTML comments, scripts, and styles removed. /// /// HTML string to fix. /// Fixed HTML string. public static string FixHtml(this string html) { // Remove comments string? withoutComments = RemoveHtmlCommentsRegex.Replace(html, string.Empty); // Remove scripts string? withoutScripts = RemoveHtmlScriptsRegex.Replace(withoutComments, string.Empty); // Remove styles string? withoutStyles = RemoveHtmlStylesRegex.Replace(withoutScripts, string.Empty); return withoutStyles; } /// /// Truncates a string to the specified length. /// /// The string to be truncated. /// The maximum length. /// Truncated string. public static string Truncate(this string? value, int length) => Truncate(value, length, false); /// /// Truncates a string to the specified length. /// /// The string to be truncated. /// The maximum length. /// true to add ellipsis to the truncated text; otherwise, false. /// Truncated string. public static string Truncate(this string? value, int length, bool ellipsis) { if (!string.IsNullOrEmpty(value)) { value = value!.Trim(); if (value.Length > length) { if (ellipsis) { return value.Substring(0, length) + "..."; } return value.Substring(0, length); } } return value ?? string.Empty; } }