-
Notifications
You must be signed in to change notification settings - Fork 324
/
Copy pathStringExtensions.cs
198 lines (169 loc) · 8.35 KB
/
StringExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// 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;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net;
using System.Text.RegularExpressions;
namespace CommunityToolkit.Common;
/// <summary>
/// Helpers for working with strings and string representations.
/// </summary>
public static class StringExtensions
{
/// <summary>
/// Regular expression for matching a phone number.
/// </summary>
internal const string PhoneNumberRegex = @"^[+]?(\d{1,3})?[\s.-]?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$";
/// <summary>
/// Regular expression for matching a string that contains only letters.
/// </summary>
internal const string CharactersRegex = "^[A-Za-z]+$";
/// <summary>
/// Regular expression for matching an email address.
/// </summary>
/// <remarks>General Email Regex (RFC 5322 Official Standard) from https://emailregex.com.</remarks>
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])+)\\])";
/// <summary>
/// Regular expression of HTML tags to remove.
/// </summary>
private const string RemoveHtmlTagsRegex = @"(?></?\w+)(?>(?:[^>'""]+|'[^']*'|""[^""]*"")*)>";
/// <summary>
/// Regular expression for removing comments from HTML.
/// </summary>
private static readonly Regex RemoveHtmlCommentsRegex = new("<!--.*?-->", RegexOptions.Singleline);
/// <summary>
/// Regular expression for removing scripts from HTML.
/// </summary>
private static readonly Regex RemoveHtmlScriptsRegex = new(@"(?s)<script.*?(/>|</script>)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
/// <summary>
/// Regular expression for removing styles from HTML.
/// </summary>
private static readonly Regex RemoveHtmlStylesRegex = new(@"(?s)<style.*?(/>|</style>)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
/// <summary>
/// Determines whether a string is a valid email address.
/// </summary>
/// <param name="str">The string to test.</param>
/// <returns><c>true</c> for a valid email address; otherwise, <c>false</c>.</returns>
public static bool IsEmail(this string str) => Regex.IsMatch(str, EmailRegex);
/// <summary>
/// Determines whether a string is a valid decimal number.
/// </summary>
/// <param name="str">The string to test.</param>
/// <returns><c>true</c> for a valid decimal number; otherwise, <c>false</c>.</returns>
public static bool IsDecimal([NotNullWhen(true)] this string? str)
{
return decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out _);
}
/// <summary>
/// Determines whether a string is valid as a currency.
/// </summary>
/// <param name="str">The string to test.</param>
/// <param name="culture">The culture to check against. If left null, <see cref="CultureInfo.CurrentCulture"/> is used.</param>
/// <returns><c>true</c> for a valid currency; otherwise, <c>false</c>.</returns>
public static bool IsCurrency([NotNullWhen(true)] this string? str, CultureInfo? culture = null)
{
return decimal.TryParse(str, NumberStyles.Currency, culture ?? CultureInfo.CurrentCulture, out _);
}
/// <summary>
/// Determines whether a string is a valid integer.
/// </summary>
/// <param name="str">The string to test.</param>
/// <returns><c>true</c> for a valid integer; otherwise, <c>false</c>.</returns>
public static bool IsNumeric([NotNullWhen(true)] this string? str)
{
return int.TryParse(str, out _);
}
/// <summary>
/// Determines whether a string is a valid phone number.
/// </summary>
/// <param name="str">The string to test.</param>
/// <returns><c>true</c> for a valid phone number; otherwise, <c>false</c>.</returns>
public static bool IsPhoneNumber(this string str) => Regex.IsMatch(str, PhoneNumberRegex);
/// <summary>
/// Determines whether a string contains only letters.
/// </summary>
/// <param name="str">The string to test.</param>
/// <returns><c>true</c> if the string contains only letters; otherwise, <c>false</c>.</returns>
public static bool IsCharacterString(this string str) => Regex.IsMatch(str, CharactersRegex);
/// <summary>
/// Returns a string with HTML comments, scripts, styles, and tags removed.
/// </summary>
/// <param name="htmlText">HTML string.</param>
/// <returns>Decoded HTML string.</returns>
[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);
}
/// <summary>
/// Returns a string with HTML comments, scripts, and styles removed.
/// </summary>
/// <param name="html">HTML string to fix.</param>
/// <returns>Fixed HTML string.</returns>
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;
}
/// <summary>
/// Truncates a string to the specified length.
/// </summary>
/// <param name="value">The string to be truncated.</param>
/// <param name="length">The maximum length.</param>
/// <returns>Truncated string.</returns>
public static string Truncate(this string? value, int length) => Truncate(value, length, false);
/// <summary>
/// Provide better linking for resourced strings.
/// </summary>
/// <param name="format">The format of the string being linked.</param>
/// <param name="args">The object which will receive the linked String.</param>
/// <returns>Truncated string.</returns>
[Obsolete("This method will be removed in a future version of the Toolkit. Use the native C# string interpolation syntax instead, see: https://docs.microsoft.com/dotnet/csharp/language-reference/tokens/interpolated")]
public static string AsFormat(this string format, params object[] args)
{
// Note: this extension was originally added to help developers using {x:Bind} in XAML, but
// due to a known limitation in the UWP/WinUI XAML compiler, using either this method or the
// standard string.Format method from the BCL directly doesn't always work. Since this method
// doesn't actually provide any benefit over the built-in one, it has been marked as obsolete.
// For more details, see the WinUI issue on the XAML compiler limitation here:
// https://github.com/microsoft/microsoft-ui-xaml/issues/2654.
return string.Format(format, args);
}
/// <summary>
/// Truncates a string to the specified length.
/// </summary>
/// <param name="value">The string to be truncated.</param>
/// <param name="length">The maximum length.</param>
/// <param name="ellipsis"><c>true</c> to add ellipsis to the truncated text; otherwise, <c>false</c>.</param>
/// <returns>Truncated string.</returns>
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;
}
}