Skip to content

Commit

Permalink
Add StringExtensions Unit Tests (#14099)
Browse files Browse the repository at this point in the history
  • Loading branch information
hishamco authored Apr 13, 2024
1 parent 9bcf6f7 commit f59edd1
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public async Task<ActionResult> CreatePOST(CreateTypeViewModel viewModel)
ModelState.AddModelError("Name", S["The Technical Name can't be empty."]);
}

if (!string.IsNullOrWhiteSpace(viewModel.Name) && !viewModel.Name[0].IsLetter())
if (!string.IsNullOrWhiteSpace(viewModel.Name) && !char.IsLetter(viewModel.Name[0]))
{
ModelState.AddModelError("Name", S["The Technical Name must start with a letter."]);
}
Expand Down Expand Up @@ -366,7 +366,7 @@ public async Task<ActionResult> AddReusablePartToPOST(string id)
ModelState.AddModelError("DisplayName", S["A part with the same Display Name already exists."]);
}

if (!string.IsNullOrWhiteSpace(viewModel.Name) && !viewModel.Name[0].IsLetter())
if (!string.IsNullOrWhiteSpace(viewModel.Name) && !char.IsLetter(viewModel.Name[0]))
{
ModelState.AddModelError("Name", S["The Technical Name must start with a letter."]);
}
Expand Down Expand Up @@ -479,7 +479,7 @@ public async Task<ActionResult> CreatePartPOST(CreatePartViewModel viewModel)
ModelState.AddModelError("Name", S["A part with the same Technical Name already exists."]);
}

if (!string.IsNullOrWhiteSpace(viewModel.Name) && !viewModel.Name[0].IsLetter())
if (!string.IsNullOrWhiteSpace(viewModel.Name) && !char.IsLetter(viewModel.Name[0]))
{
ModelState.AddModelError("Name", S["The Technical Name must start with a letter."]);
}
Expand Down Expand Up @@ -674,7 +674,7 @@ public async Task<ActionResult> AddFieldToPOST(AddFieldViewModel viewModel, stri
ModelState.AddModelError("Name", S["A field with the same Technical Name already exists."]);
}

if (!string.IsNullOrWhiteSpace(viewModel.Name) && !viewModel.Name[0].IsLetter())
if (!string.IsNullOrWhiteSpace(viewModel.Name) && !char.IsLetter(viewModel.Name[0]))
{
ModelState.AddModelError("Name", S["The Technical Name must start with a letter."]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public ContentPartDefinitionBuilder(ContentPartDefinition existing)

public ContentPartDefinition Build()
{
if (!Name[0].IsLetter())
if (!char.IsLetter(Name[0]))
{
throw new ArgumentException("Content part name must start with a letter", "name");
}
Expand Down Expand Up @@ -228,7 +228,7 @@ public FieldConfigurerImpl(ContentPartFieldDefinition field, ContentPartDefiniti

public override ContentPartFieldDefinition Build()
{
if (!_fieldName[0].IsLetter())
if (!char.IsLetter(_fieldName[0]))
{
throw new ArgumentException("Content field name must start with a letter", "name");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public ContentTypeDefinitionBuilder(ContentTypeDefinition existing)

public ContentTypeDefinition Build()
{
if (!_name[0].IsLetter())
if (!char.IsLetter(_name[0]))
{
throw new ArgumentException("Content type name must start with a letter", "name");
}
Expand Down Expand Up @@ -208,7 +208,7 @@ public PartConfigurerImpl(ContentTypePartDefinition part)

public override ContentTypePartDefinition Build()
{
if (!Current.Name[0].IsLetter())
if (!char.IsLetter(Current.Name[0]))
{
throw new ArgumentException("Content part name must start with a letter", "name");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ namespace OrchardCore.ContentManagement.Utilities
{
public static class StringExtensions
{
private static readonly char[] _validSegmentChars = "/?#[]@\"^{}|`<>\t\r\n\f ".ToCharArray();

private static readonly HashSet<string> _reservedNames = new(StringComparer.OrdinalIgnoreCase)
{
nameof(ContentItem.Id),
nameof(ContentItem.ContentItemId),
nameof(ContentItem.ContentItemVersionId),
nameof(ContentItem.ContentType),
nameof(ContentItem.Published),
nameof(ContentItem.Latest),
nameof(ContentItem.ModifiedUtc),
nameof(ContentItem.PublishedUtc),
nameof(ContentItem.CreatedUtc),
nameof(ContentItem.Owner),
nameof(ContentItem.Author),
nameof(ContentItem.DisplayText),
};

public static string CamelFriendly(this string camel)
{
// optimize common cases
Expand All @@ -33,28 +51,29 @@ public static string CamelFriendly(this string camel)
return sb.ToString();
}

public static string Ellipsize(this string text, int characterCount)
{
return text.Ellipsize(characterCount, "\u00A0\u2026");
}
public static string Ellipsize(this string text, int characterCount) => text.Ellipsize(characterCount, "\u00A0\u2026");

public static string Ellipsize(this string text, int characterCount, string ellipsis, bool wordBoundary = false)
{
if (string.IsNullOrWhiteSpace(text))
return "";
{
return string.Empty;
}

if (characterCount < 0 || text.Length <= characterCount)
{
return text;
}

// search beginning of word
var backup = characterCount;
while (characterCount > 0 && text[characterCount - 1].IsLetter())
while (characterCount > 0 && char.IsLetter(text[characterCount - 1]))
{
characterCount--;
}

// search previous word
while (characterCount > 0 && text[characterCount - 1].IsSpace())
while (characterCount > 0 && char.IsWhiteSpace(text[characterCount - 1]))
{
characterCount--;
}
Expand All @@ -66,15 +85,14 @@ public static string Ellipsize(this string text, int characterCount, string elli
}

var trimmed = text[..characterCount];

return trimmed + ellipsis;
}

public static LocalizedString OrDefault(this string text, LocalizedString defaultValue)
{
return string.IsNullOrEmpty(text)
=> string.IsNullOrEmpty(text)
? defaultValue
: new LocalizedString(null, text);
}

public static string RemoveTags(this string html, bool htmlDecode = false)
{
Expand Down Expand Up @@ -119,16 +137,12 @@ public static string RemoveTags(this string html, bool htmlDecode = false)

// not accounting for only \r (e.g. Apple OS 9 carriage return only new lines)
public static string ReplaceNewLinesWith(this string text, string replacement)
{
return string.IsNullOrWhiteSpace(text)
? string.Empty
: text
.Replace("\r\n", "\r\r")
.Replace("\n", string.Format(replacement, "\r\n"))
.Replace("\r\r", string.Format(replacement, "\r\n"));
}
=> string.IsNullOrWhiteSpace(text)
? string.Empty
: text.Replace("\r\n", "\r\r")
.Replace("\n", string.Format(replacement, "\r\n"))
.Replace("\r\r", string.Format(replacement, "\r\n"));

private static readonly char[] _validSegmentChars = "/?#[]@\"^{}|`<>\t\r\n\f ".ToCharArray();
public static bool IsValidUrlSegment(this string segment)
{
// valid isegment from rfc3987 - http://tools.ietf.org/html/rfc3987#page-8
Expand Down Expand Up @@ -159,15 +173,11 @@ public static string ToSafeName(this string name)
}

name = RemoveDiacritics(name);
name = name.Strip(c =>
!c.IsLetter()
&& !char.IsDigit(c)
);

name = name.Trim();
name = name.Strip(c => !char.IsLetter(c) && !char.IsDigit(c)).Trim();

// don't allow non A-Z chars as first letter, as they are not allowed in prefixes
while (name.Length > 0 && !IsLetter(name[0]))
while (name.Length > 0 && !char.IsLetter(name[0]))
{
name = name[1..];
}
Expand All @@ -180,45 +190,20 @@ public static string ToSafeName(this string name)
return name;
}

private static readonly HashSet<string> _reservedNames = new(StringComparer.OrdinalIgnoreCase)
{
nameof(ContentItem.Id),
nameof(ContentItem.ContentItemId),
nameof(ContentItem.ContentItemVersionId),
nameof(ContentItem.ContentType),
nameof(ContentItem.Published),
nameof(ContentItem.Latest),
nameof(ContentItem.ModifiedUtc),
nameof(ContentItem.PublishedUtc),
nameof(ContentItem.CreatedUtc),
nameof(ContentItem.Owner),
nameof(ContentItem.Author),
nameof(ContentItem.DisplayText),
};

public static bool IsReservedContentName(this string name)
{
if (_reservedNames.Contains(name))
{
return true;
}

return false;
}

/// <summary>
/// Whether the char is a letter between A and Z or not.
/// </summary>
[Obsolete("Use Char.IsLetter() instead.")]
public static bool IsLetter(this char c)
{
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
}

[Obsolete("Use Char.IsWhiteSpace() instead.")]
public static bool IsSpace(this char c)
{
return (c == '\r' || c == '\n' || c == '\t' || c == '\f' || c == ' ');
}

public static bool IsReservedContentName(this string name) => _reservedNames.Contains(name);

public static string RemoveDiacritics(this string name)
{
var stFormD = name.Normalize(NormalizationForm.FormD);
Expand All @@ -233,22 +218,22 @@ public static string RemoveDiacritics(this string name)
}
}

return (sb.ToString().Normalize(NormalizationForm.FormC));
return sb.ToString().Normalize(NormalizationForm.FormC);
}

public static string Strip(this string subject, params char[] stripped)
public static string Strip(this string source, params char[] stripped)
{
if (stripped == null || stripped.Length == 0 || string.IsNullOrEmpty(subject))
if (stripped == null || stripped.Length == 0 || string.IsNullOrEmpty(source))
{
return subject;
return source;
}

var result = new char[subject.Length];
var result = new char[source.Length];

var cursor = 0;
for (var i = 0; i < subject.Length; i++)
for (var i = 0; i < source.Length; i++)
{
var current = subject[i];
var current = source[i];
if (Array.IndexOf(stripped, current) < 0)
{
result[cursor++] = current;
Expand All @@ -258,14 +243,14 @@ public static string Strip(this string subject, params char[] stripped)
return new string(result, 0, cursor);
}

public static string Strip(this string subject, Func<char, bool> predicate)
public static string Strip(this string source, Func<char, bool> predicate)
{
var result = new char[subject.Length];
var result = new char[source.Length];

var cursor = 0;
for (var i = 0; i < subject.Length; i++)
for (var i = 0; i < source.Length; i++)
{
var current = subject[i];
var current = source[i];
if (!predicate(current))
{
result[cursor++] = current;
Expand All @@ -275,16 +260,16 @@ public static string Strip(this string subject, Func<char, bool> predicate)
return new string(result, 0, cursor);
}

public static bool Any(this string subject, params char[] chars)
public static bool Any(this string source, params char[] chars)
{
if (string.IsNullOrEmpty(subject) || chars == null || chars.Length == 0)
if (string.IsNullOrEmpty(source) || chars == null || chars.Length == 0)
{
return false;
}

for (var i = 0; i < subject.Length; i++)
for (var i = 0; i < source.Length; i++)
{
var current = subject[i];
var current = source[i];
if (Array.IndexOf(chars, current) >= 0)
{
return true;
Expand All @@ -294,21 +279,16 @@ public static bool Any(this string subject, params char[] chars)
return false;
}

public static bool All(this string subject, params char[] chars)
public static bool All(this string source, params char[] chars)
{
if (string.IsNullOrEmpty(subject))
{
return true;
}

if (chars == null || chars.Length == 0)
if (string.IsNullOrEmpty(source) || chars == null || chars.Length == 0)
{
return false;
}

for (var i = 0; i < subject.Length; i++)
for (var i = 0; i < source.Length; i++)
{
var current = subject[i];
var current = source[i];
if (Array.IndexOf(chars, current) < 0)
{
return false;
Expand Down Expand Up @@ -361,23 +341,36 @@ public static string Translate(this string subject, char[] from, char[] to)
public static string ReplaceAll(this string original, IDictionary<string, string> replacements)
{
var pattern = $"{string.Join("|", replacements.Keys)}";

return Regex.Replace(original, pattern, match => replacements[match.Value]);
}

public static string TrimEnd(this string rough, string trim = "")
public static string TrimEnd(this string value, string trim = "")
{
if (rough == null)
if (value == null)
{
return null;
}

return rough.EndsWith(trim, StringComparison.Ordinal)
? rough[..^trim.Length]
: rough;
return value.EndsWith(trim, StringComparison.Ordinal)
? value[..^trim.Length]
: value;
}

public static string ReplaceLastOccurrence(this string source, string find, string replace)
public static string ReplaceLastOccurrence(this string source, string searchedValue, string replacedValue)
{
var place = source.LastIndexOf(find, StringComparison.Ordinal);
return source.Remove(place, find.Length).Insert(place, replace);
if (searchedValue is null || replacedValue is null)
{
return source;
}

var lastIndex = source.LastIndexOf(searchedValue, StringComparison.Ordinal);
if (lastIndex == -1)
{
return source;
}

return source.Remove(lastIndex, searchedValue.Length).Insert(lastIndex, replacedValue);
}
}
}
Loading

0 comments on commit f59edd1

Please sign in to comment.