Headless CMS and Content Managment Hub
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

807 lines
23 KiB

// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Squidex.Infrastructure
{
public static class StringExtensions
{
private const char NullChar = (char)0;
private static readonly Regex SlugRegex = new Regex("^[a-z0-9]+(\\-[a-z0-9]+)*$", RegexOptions.Compiled);
private static readonly Regex EmailRegex = new Regex(@"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-||_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+([a-z]+|\d|-|\.{0,1}|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex PropertyNameRegex = new Regex("^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*$", RegexOptions.Compiled);
private static readonly Dictionary<char, string> LowerCaseDiacritics;
private static readonly Dictionary<char, string> Diacritics = new Dictionary<char, string>
{
['$'] = "dollar",
['%'] = "percent",
['&'] = "and",
['<'] = "less",
['>'] = "greater",
['|'] = "or",
['¢'] = "cent",
['£'] = "pound",
['¤'] = "currency",
['¥'] = "yen",
['©'] = "(c)",
['ª'] = "a",
['®'] = "(r)",
['º'] = "o",
['À'] = "A",
['Á'] = "A",
['Â'] = "A",
['Ã'] = "A",
['Ä'] = "AE",
['Å'] = "A",
['Æ'] = "AE",
['Ç'] = "C",
['Ə'] = "E",
['È'] = "E",
['É'] = "E",
['Ê'] = "E",
['Ë'] = "E",
['Ì'] = "I",
['Í'] = "I",
['Î'] = "I",
['Ï'] = "I",
['Ð'] = "D",
['Ñ'] = "N",
['Ò'] = "O",
['Ó'] = "O",
['Ô'] = "O",
['Õ'] = "O",
['Ö'] = "OE",
['Ø'] = "O",
['Ù'] = "U",
['Ú'] = "U",
['Û'] = "U",
['Ü'] = "UE",
['Ý'] = "Y",
['Þ'] = "TH",
['ß'] = "ss",
['à'] = "a",
['á'] = "a",
['â'] = "a",
['ã'] = "a",
['ä'] = "ae",
['å'] = "a",
['æ'] = "ae",
['ç'] = "c",
['ə'] = "e",
['è'] = "e",
['é'] = "e",
['ê'] = "e",
['ë'] = "e",
['ì'] = "i",
['í'] = "i",
['î'] = "i",
['ï'] = "i",
['ð'] = "d",
['ñ'] = "n",
['ò'] = "o",
['ó'] = "o",
['ô'] = "o",
['õ'] = "o",
['ö'] = "oe",
['ø'] = "o",
['ù'] = "u",
['ú'] = "u",
['û'] = "u",
['ü'] = "ue",
['ý'] = "y",
['þ'] = "th",
['ÿ'] = "y",
['Ā'] = "A",
['ā'] = "a",
['Ă'] = "A",
['ă'] = "a",
['Ą'] = "A",
['ą'] = "a",
['Ć'] = "C",
['ć'] = "c",
['Č'] = "C",
['č'] = "c",
['Ď'] = "D",
['ď'] = "d",
['Đ'] = "DJ",
['đ'] = "dj",
['Ē'] = "E",
['ē'] = "e",
['Ė'] = "E",
['ė'] = "e",
['Ę'] = "e",
['ę'] = "e",
['Ě'] = "E",
['ě'] = "e",
['Ğ'] = "G",
['ğ'] = "g",
['Ģ'] = "G",
['ģ'] = "g",
['Ĩ'] = "I",
['ĩ'] = "i",
['Ī'] = "i",
['ī'] = "i",
['Į'] = "I",
['į'] = "i",
['İ'] = "I",
['ı'] = "i",
['Ķ'] = "k",
['ķ'] = "k",
['Ļ'] = "L",
['ļ'] = "l",
['Ľ'] = "L",
['ľ'] = "l",
['Ł'] = "L",
['ł'] = "l",
['Ń'] = "N",
['ń'] = "n",
['Ņ'] = "N",
['ņ'] = "n",
['Ň'] = "N",
['ň'] = "n",
['Ő'] = "O",
['ő'] = "o",
['Œ'] = "OE",
['œ'] = "oe",
['Ŕ'] = "R",
['ŕ'] = "r",
['Ř'] = "R",
['ř'] = "r",
['Ś'] = "S",
['ś'] = "s",
['Ş'] = "S",
['ş'] = "s",
['Š'] = "S",
['š'] = "s",
['Ţ'] = "T",
['ţ'] = "t",
['Ť'] = "T",
['ť'] = "t",
['Ũ'] = "U",
['ũ'] = "u",
['Ū'] = "u",
['ū'] = "u",
['Ů'] = "U",
['ů'] = "u",
['Ű'] = "U",
['ű'] = "u",
['Ų'] = "U",
['ų'] = "u",
['Ź'] = "Z",
['ź'] = "z",
['Ż'] = "Z",
['ż'] = "z",
['Ž'] = "Z",
['ž'] = "z",
['ƒ'] = "f",
['Ơ'] = "O",
['ơ'] = "o",
['Ư'] = "U",
['ư'] = "u",
['Lj'] = "LJ",
['lj'] = "lj",
['Nj'] = "NJ",
['nj'] = "nj",
['Ș'] = "S",
['ș'] = "s",
['Ț'] = "T",
['ț'] = "t",
['˚'] = "o",
['Ά'] = "A",
['Έ'] = "E",
['Ή'] = "H",
['Ί'] = "I",
['Ό'] = "O",
['Ύ'] = "Y",
['Ώ'] = "W",
['ΐ'] = "i",
['Α'] = "A",
['Β'] = "B",
['Γ'] = "G",
['Δ'] = "D",
['Ε'] = "E",
['Ζ'] = "Z",
['Η'] = "H",
['Θ'] = "8",
['Ι'] = "I",
['Κ'] = "K",
['Λ'] = "L",
['Μ'] = "M",
['Ν'] = "N",
['Ξ'] = "3",
['Ο'] = "O",
['Π'] = "P",
['Ρ'] = "R",
['Σ'] = "S",
['Τ'] = "T",
['Υ'] = "Y",
['Φ'] = "F",
['Χ'] = "X",
['Ψ'] = "PS",
['Ω'] = "W",
['Ϊ'] = "I",
['Ϋ'] = "Y",
['ά'] = "a",
['έ'] = "e",
['ή'] = "h",
['ί'] = "i",
['ΰ'] = "y",
['α'] = "a",
['β'] = "b",
['γ'] = "g",
['δ'] = "d",
['ε'] = "e",
['ζ'] = "z",
['η'] = "h",
['θ'] = "8",
['ι'] = "i",
['κ'] = "k",
['λ'] = "l",
['μ'] = "m",
['ν'] = "n",
['ξ'] = "3",
['ο'] = "o",
['π'] = "p",
['ρ'] = "r",
['ς'] = "s",
['σ'] = "s",
['τ'] = "t",
['υ'] = "y",
['φ'] = "f",
['χ'] = "x",
['ψ'] = "ps",
['ω'] = "w",
['ϊ'] = "i",
['ϋ'] = "y",
['ό'] = "o",
['ύ'] = "y",
['ώ'] = "w",
['Ё'] = "Yo",
['Ђ'] = "DJ",
['Є'] = "Ye",
['І'] = "I",
['Ї'] = "Yi",
['Ј'] = "J",
['Љ'] = "LJ",
['Њ'] = "NJ",
['Ћ'] = "C",
['Џ'] = "DZ",
['А'] = "A",
['Б'] = "B",
['В'] = "V",
['Г'] = "G",
['Д'] = "D",
['Е'] = "E",
['Ж'] = "Zh",
['З'] = "Z",
['И'] = "I",
['Й'] = "J",
['К'] = "K",
['Л'] = "L",
['М'] = "M",
['Н'] = "N",
['О'] = "O",
['П'] = "P",
['Р'] = "R",
['С'] = "S",
['Т'] = "T",
['У'] = "U",
['Ф'] = "F",
['Х'] = "H",
['Ц'] = "C",
['Ч'] = "Ch",
['Ш'] = "Sh",
['Щ'] = "Sh",
['Ъ'] = "U",
['Ы'] = "Y",
['Ь'] = "b",
['Э'] = "E",
['Ю'] = "Yu",
['Я'] = "Ya",
['а'] = "a",
['б'] = "b",
['в'] = "v",
['г'] = "g",
['д'] = "d",
['е'] = "e",
['ж'] = "zh",
['з'] = "z",
['и'] = "i",
['й'] = "j",
['к'] = "k",
['л'] = "l",
['м'] = "m",
['н'] = "n",
['о'] = "o",
['п'] = "p",
['р'] = "r",
['с'] = "s",
['т'] = "t",
['у'] = "u",
['ф'] = "f",
['х'] = "h",
['ц'] = "c",
['ч'] = "ch",
['ш'] = "sh",
['щ'] = "sh",
['ъ'] = "u",
['ы'] = "y",
['ь'] = "s",
['э'] = "e",
['ю'] = "yu",
['я'] = "ya",
['ё'] = "yo",
['ђ'] = "dj",
['є'] = "ye",
['і'] = "i",
['ї'] = "yi",
['ј'] = "j",
['љ'] = "lj",
['њ'] = "nj",
['ћ'] = "c",
['џ'] = "dz",
['Ґ'] = "G",
['ґ'] = "g",
['฿'] = "baht",
['ა'] = "a",
['ბ'] = "b",
['გ'] = "g",
['დ'] = "d",
['ე'] = "e",
['ვ'] = "v",
['ზ'] = "z",
['თ'] = "t",
['ი'] = "i",
['კ'] = "k",
['ლ'] = "l",
['მ'] = "m",
['ნ'] = "n",
['ო'] = "o",
['პ'] = "p",
['ჟ'] = "zh",
['რ'] = "r",
['ს'] = "s",
['ტ'] = "t",
['უ'] = "u",
['ფ'] = "f",
['ქ'] = "k",
['ღ'] = "gh",
['ყ'] = "q",
['შ'] = "sh",
['ჩ'] = "ch",
['ც'] = "ts",
['ძ'] = "dz",
['წ'] = "ts",
['ჭ'] = "ch",
['ხ'] = "kh",
['ჯ'] = "j",
['ჰ'] = "h",
['ẞ'] = "SS",
['Ạ'] = "A",
['ạ'] = "a",
['Ả'] = "A",
['ả'] = "a",
['Ấ'] = "A",
['ấ'] = "a",
['Ầ'] = "A",
['ầ'] = "a",
['Ẩ'] = "A",
['ẩ'] = "a",
['Ẫ'] = "A",
['ẫ'] = "a",
['Ậ'] = "A",
['ậ'] = "a",
['Ắ'] = "A",
['ắ'] = "a",
['Ằ'] = "A",
['ằ'] = "a",
['Ẳ'] = "A",
['ẳ'] = "a",
['Ẵ'] = "A",
['ẵ'] = "a",
['Ặ'] = "A",
['ặ'] = "a",
['Ẹ'] = "E",
['ẹ'] = "e",
['Ẻ'] = "E",
['ẻ'] = "e",
['Ẽ'] = "E",
['ẽ'] = "e",
['Ế'] = "E",
['ế'] = "e",
['Ề'] = "E",
['ề'] = "e",
['Ể'] = "E",
['ể'] = "e",
['Ễ'] = "E",
['ễ'] = "e",
['Ệ'] = "E",
['ệ'] = "e",
['Ỉ'] = "I",
['ỉ'] = "i",
['Ị'] = "I",
['ị'] = "i",
['Ọ'] = "O",
['ọ'] = "o",
['Ỏ'] = "O",
['ỏ'] = "o",
['Ố'] = "O",
['ố'] = "o",
['Ồ'] = "O",
['ồ'] = "o",
['Ổ'] = "O",
['ổ'] = "o",
['Ỗ'] = "O",
['ỗ'] = "o",
['Ộ'] = "O",
['ộ'] = "o",
['Ớ'] = "O",
['ớ'] = "o",
['Ờ'] = "O",
['ờ'] = "o",
['Ở'] = "O",
['ở'] = "o",
['Ỡ'] = "O",
['ỡ'] = "o",
['Ợ'] = "O",
['ợ'] = "o",
['Ụ'] = "U",
['ụ'] = "u",
['Ủ'] = "U",
['ủ'] = "u",
['Ứ'] = "U",
['ứ'] = "u",
['Ừ'] = "U",
['ừ'] = "u",
['Ử'] = "U",
['ử'] = "u",
['Ữ'] = "U",
['ữ'] = "u",
['Ự'] = "U",
['ự'] = "u",
['Ỳ'] = "Y",
['ỳ'] = "y",
['Ỵ'] = "Y",
['ỵ'] = "y",
['Ỷ'] = "Y",
['ỷ'] = "y",
['Ỹ'] = "Y",
['ỹ'] = "y",
['‘'] = "\'",
['’'] = "\'",
['“'] = "\\\"",
['”'] = "\\\"",
['†'] = "+",
['•'] = "*",
['…'] = "...",
['₠'] = "ecu",
['₢'] = "cruzeiro",
['₣'] = "french franc",
['₤'] = "lira",
['₥'] = "mill",
['₦'] = "naira",
['₧'] = "peseta",
['₨'] = "rupee",
['₩'] = "won",
['₪'] = "new shequel",
['₫'] = "dong",
['€'] = "euro",
['₭'] = "kip",
['₮'] = "tugrik",
['₯'] = "drachma",
['₰'] = "penny",
['₱'] = "peso",
['₲'] = "guarani",
['₳'] = "austral",
['₴'] = "hryvnia",
['₵'] = "cedi",
['₹'] = "indian rupee",
['₽'] = "russian ruble",
['₿'] = "bitcoin",
['℠'] = "sm",
['™'] = "tm",
['∂'] = "d",
['∆'] = "delta",
['∑'] = "sum",
['∞'] = "infinity",
['♥'] = "love",
['元'] = "yuan",
['円'] = "yen",
['﷼'] = "rial"
};
static StringExtensions()
{
LowerCaseDiacritics = Diacritics.ToDictionary(x => x.Key, x => x.Value.ToLowerInvariant());
}
public static bool IsSlug(this string? value)
{
return value != null && SlugRegex.IsMatch(value);
}
public static bool IsEmail(this string? value)
{
return value != null && EmailRegex.IsMatch(value);
}
public static bool IsPropertyName(this string? value)
{
return value != null && PropertyNameRegex.IsMatch(value);
}
public static string Or(this string? value, string fallback)
{
return !string.IsNullOrWhiteSpace(value) ? value.Trim() : fallback;
}
public static string ToPascalCase(this string value)
{
if (value.Length == 0)
{
return string.Empty;
}
var sb = new StringBuilder(value.Length);
var last = NullChar;
var length = 0;
for (var i = 0; i < value.Length; i++)
{
var c = value[i];
if (c == '-' || c == '_' || c == ' ')
{
if (last != NullChar)
{
sb.Append(char.ToUpperInvariant(last));
}
last = NullChar;
length = 0;
}
else
{
if (length > 1)
{
sb.Append(c);
}
else if (length == 0)
{
last = c;
}
else
{
sb.Append(char.ToUpperInvariant(last));
sb.Append(c);
last = NullChar;
}
length++;
}
}
if (last != NullChar)
{
sb.Append(char.ToUpperInvariant(last));
}
return sb.ToString();
}
public static string ToKebabCase(this string value)
{
if (value.Length == 0)
{
return string.Empty;
}
var sb = new StringBuilder(value.Length);
var length = 0;
for (var i = 0; i < value.Length; i++)
{
var c = value[i];
if (c == '-' || c == '_' || c == ' ')
{
length = 0;
}
else
{
if (length > 0)
{
sb.Append(char.ToLowerInvariant(c));
}
else
{
if (sb.Length > 0)
{
sb.Append('-');
}
sb.Append(char.ToLowerInvariant(c));
}
length++;
}
}
return sb.ToString();
}
public static string ToCamelCase(this string value)
{
if (value.Length == 0)
{
return string.Empty;
}
var sb = new StringBuilder(value.Length);
var last = NullChar;
var length = 0;
for (var i = 0; i < value.Length; i++)
{
var c = value[i];
if (c == '-' || c == '_' || c == ' ')
{
if (last != NullChar)
{
if (sb.Length > 0)
{
sb.Append(char.ToUpperInvariant(last));
}
else
{
sb.Append(char.ToLowerInvariant(last));
}
}
last = NullChar;
length = 0;
}
else
{
if (length > 1)
{
sb.Append(c);
}
else if (length == 0)
{
last = c;
}
else
{
if (sb.Length > 0)
{
sb.Append(char.ToUpperInvariant(last));
}
else
{
sb.Append(char.ToLowerInvariant(last));
}
sb.Append(c);
last = NullChar;
}
length++;
}
}
if (last != NullChar)
{
if (sb.Length > 0)
{
sb.Append(char.ToUpperInvariant(last));
}
else
{
sb.Append(char.ToLowerInvariant(last));
}
}
return sb.ToString();
}
public static string Slugify(this string value, ISet<char>? preserveHash = null, bool singleCharDiactric = false, char separator = '-')
{
var result = new StringBuilder(value.Length);
var lastChar = (char)0;
for (var i = 0; i < value.Length; i++)
{
var character = value[i];
if (preserveHash?.Contains(character) == true)
{
result.Append(character);
}
else if (char.IsLetter(character) || char.IsNumber(character))
{
lastChar = character;
var lower = char.ToLowerInvariant(character);
if (LowerCaseDiacritics.TryGetValue(character, out var replacement))
{
if (singleCharDiactric && replacement.Length == 2)
{
result.Append(replacement[0]);
}
else
{
result.Append(replacement);
}
}
else
{
result.Append(lower);
}
}
else if ((i < value.Length - 1) && (i > 0 && lastChar != separator))
{
lastChar = separator;
result.Append(separator);
}
}
return result.ToString().Trim(separator);
}
public static string BuildFullUrl(this string baseUrl, string path, bool trailingSlash = false)
{
Guard.NotNull(path, nameof(path));
var url = $"{baseUrl.TrimEnd('/')}/{path.Trim('/')}";
if (trailingSlash &&
url.IndexOf("#", StringComparison.OrdinalIgnoreCase) < 0 &&
url.IndexOf("?", StringComparison.OrdinalIgnoreCase) < 0 &&
url.IndexOf(";", StringComparison.OrdinalIgnoreCase) < 0)
{
url += "/";
}
return url;
}
public static string JoinNonEmpty(string separator, params string?[] parts)
{
Guard.NotNull(separator, nameof(separator));
if (parts == null || parts.Length == 0)
{
return string.Empty;
}
return string.Join(separator, parts.Where(x => !string.IsNullOrWhiteSpace(x)));
}
public static string ToIso8601(this DateTime value)
{
return value.ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture);
}
}
}