using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using ICU4N.Text; namespace Jellyfin.Extensions { /// /// Provides extensions methods for . /// public static partial class StringExtensions { private static readonly Lazy _transliteratorId = new(() => Environment.GetEnvironmentVariable("JELLYFIN_TRANSLITERATOR_ID") ?? "Any-Latin; Latin-Ascii; Lower; NFD; [:Nonspacing Mark:] Remove; [:Punctuation:] Remove;"); private static readonly Lazy _transliterator = new(() => { try { return Transliterator.GetInstance(_transliteratorId.Value); } catch (ArgumentException) { return null; } }); // Matches non-conforming unicode chars // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/ [GeneratedRegex("([\ud800-\udbff](?![\udc00-\udfff]))|((? /// Removes the diacritics character from the strings. /// /// The string to act on. /// The string without diacritics character. public static string RemoveDiacritics(this string text) => Diacritics.Extensions.StringExtensions.RemoveDiacritics( NonConformingUnicodeRegex().Replace(text, string.Empty)); /// /// Checks whether or not the specified string has diacritics in it. /// /// The string to check. /// True if the string has diacritics, false otherwise. public static bool HasDiacritics(this string text) => Diacritics.Extensions.StringExtensions.HasDiacritics(text) || NonConformingUnicodeRegex().IsMatch(text); /// /// Counts the number of occurrences of [needle] in the string. /// /// The haystack to search in. /// The character to search for. /// The number of occurrences of the [needle] character. public static int Count(this ReadOnlySpan value, char needle) { var count = 0; var length = value.Length; for (var i = 0; i < length; i++) { if (value[i] == needle) { count++; } } return count; } /// /// Returns the part on the left of the needle. /// /// The string to seek. /// The needle to find. /// The part left of the . public static ReadOnlySpan LeftPart(this ReadOnlySpan haystack, char needle) { if (haystack.IsEmpty) { return ReadOnlySpan.Empty; } var pos = haystack.IndexOf(needle); return pos == -1 ? haystack : haystack[..pos]; } /// /// Returns the part on the right of the needle. /// /// The string to seek. /// The needle to find. /// The part right of the . public static ReadOnlySpan RightPart(this ReadOnlySpan haystack, char needle) { if (haystack.IsEmpty) { return ReadOnlySpan.Empty; } var pos = haystack.LastIndexOf(needle); if (pos == -1) { return haystack; } if (pos == haystack.Length - 1) { return ReadOnlySpan.Empty; } return haystack[(pos + 1)..]; } /// /// Returns a transliterated string which only contain ascii characters. /// /// The string to act on. /// The transliterated string. public static string Transliterated(this string text) { return (_transliterator.Value is null) ? text : _transliterator.Value.Transliterate(text); } /// /// Ensures all strings are non-null and trimmed of leading an trailing blanks. /// /// The enumerable of strings to trim. /// The enumeration of trimmed strings. public static IEnumerable Trimmed(this IEnumerable values) { return values.Select(i => (i ?? string.Empty).Trim()); } /// /// Truncates a string at the first null character ('\0'). /// /// The input string. /// /// The substring up to (but not including) the first null character, /// or the original string if no null character is present. /// public static string TruncateAtNull(this string text) { return string.IsNullOrEmpty(text) ? text : text.AsSpan().LeftPart('\0').ToString(); } } }