| | 1 | | using System; |
| | 2 | | using System.Text.RegularExpressions; |
| | 3 | | using ICU4N.Text; |
| | 4 | |
|
| | 5 | | namespace Jellyfin.Extensions |
| | 6 | | { |
| | 7 | | /// <summary> |
| | 8 | | /// Provides extensions methods for <see cref="string" />. |
| | 9 | | /// </summary> |
| | 10 | | public static partial class StringExtensions |
| | 11 | | { |
| 0 | 12 | | private static readonly Lazy<string> _transliteratorId = new(() => |
| 0 | 13 | | Environment.GetEnvironmentVariable("JELLYFIN_TRANSLITERATOR_ID") |
| 0 | 14 | | ?? "Any-Latin; Latin-Ascii; Lower; NFD; [:Nonspacing Mark:] Remove; [:Punctuation:] Remove;"); |
| | 15 | |
|
| 0 | 16 | | private static readonly Lazy<Transliterator?> _transliterator = new(() => |
| 0 | 17 | | { |
| 0 | 18 | | try |
| 0 | 19 | | { |
| 0 | 20 | | return Transliterator.GetInstance(_transliteratorId.Value); |
| 0 | 21 | | } |
| 0 | 22 | | catch (ArgumentException) |
| 0 | 23 | | { |
| 0 | 24 | | return null; |
| 0 | 25 | | } |
| 0 | 26 | | }); |
| | 27 | |
|
| | 28 | | // Matches non-conforming unicode chars |
| | 29 | | // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/ |
| | 30 | |
|
| | 31 | | [GeneratedRegex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(�)")] |
| | 32 | | private static partial Regex NonConformingUnicodeRegex(); |
| | 33 | |
|
| | 34 | | /// <summary> |
| | 35 | | /// Removes the diacritics character from the strings. |
| | 36 | | /// </summary> |
| | 37 | | /// <param name="text">The string to act on.</param> |
| | 38 | | /// <returns>The string without diacritics character.</returns> |
| | 39 | | public static string RemoveDiacritics(this string text) |
| 196 | 40 | | => Diacritics.Extensions.StringExtensions.RemoveDiacritics( |
| 196 | 41 | | NonConformingUnicodeRegex().Replace(text, string.Empty)); |
| | 42 | |
|
| | 43 | | /// <summary> |
| | 44 | | /// Checks whether or not the specified string has diacritics in it. |
| | 45 | | /// </summary> |
| | 46 | | /// <param name="text">The string to check.</param> |
| | 47 | | /// <returns>True if the string has diacritics, false otherwise.</returns> |
| | 48 | | public static bool HasDiacritics(this string text) |
| 12 | 49 | | => Diacritics.Extensions.StringExtensions.HasDiacritics(text) |
| 12 | 50 | | || NonConformingUnicodeRegex().IsMatch(text); |
| | 51 | |
|
| | 52 | | /// <summary> |
| | 53 | | /// Counts the number of occurrences of [needle] in the string. |
| | 54 | | /// </summary> |
| | 55 | | /// <param name="value">The haystack to search in.</param> |
| | 56 | | /// <param name="needle">The character to search for.</param> |
| | 57 | | /// <returns>The number of occurrences of the [needle] character.</returns> |
| | 58 | | public static int Count(this ReadOnlySpan<char> value, char needle) |
| | 59 | | { |
| 8 | 60 | | var count = 0; |
| 8 | 61 | | var length = value.Length; |
| 1554 | 62 | | for (var i = 0; i < length; i++) |
| | 63 | | { |
| 769 | 64 | | if (value[i] == needle) |
| | 65 | | { |
| 14 | 66 | | count++; |
| | 67 | | } |
| | 68 | | } |
| | 69 | |
|
| 8 | 70 | | return count; |
| | 71 | | } |
| | 72 | |
|
| | 73 | | /// <summary> |
| | 74 | | /// Returns the part on the left of the <c>needle</c>. |
| | 75 | | /// </summary> |
| | 76 | | /// <param name="haystack">The string to seek.</param> |
| | 77 | | /// <param name="needle">The needle to find.</param> |
| | 78 | | /// <returns>The part left of the <paramref name="needle" />.</returns> |
| | 79 | | public static ReadOnlySpan<char> LeftPart(this ReadOnlySpan<char> haystack, char needle) |
| | 80 | | { |
| 374 | 81 | | if (haystack.IsEmpty) |
| | 82 | | { |
| 2 | 83 | | return ReadOnlySpan<char>.Empty; |
| | 84 | | } |
| | 85 | |
|
| 372 | 86 | | var pos = haystack.IndexOf(needle); |
| 372 | 87 | | return pos == -1 ? haystack : haystack[..pos]; |
| | 88 | | } |
| | 89 | |
|
| | 90 | | /// <summary> |
| | 91 | | /// Returns the part on the right of the <c>needle</c>. |
| | 92 | | /// </summary> |
| | 93 | | /// <param name="haystack">The string to seek.</param> |
| | 94 | | /// <param name="needle">The needle to find.</param> |
| | 95 | | /// <returns>The part right of the <paramref name="needle" />.</returns> |
| | 96 | | public static ReadOnlySpan<char> RightPart(this ReadOnlySpan<char> haystack, char needle) |
| | 97 | | { |
| 10 | 98 | | if (haystack.IsEmpty) |
| | 99 | | { |
| 1 | 100 | | return ReadOnlySpan<char>.Empty; |
| | 101 | | } |
| | 102 | |
|
| 9 | 103 | | var pos = haystack.LastIndexOf(needle); |
| 9 | 104 | | if (pos == -1) |
| | 105 | | { |
| 1 | 106 | | return haystack; |
| | 107 | | } |
| | 108 | |
|
| 8 | 109 | | if (pos == haystack.Length - 1) |
| | 110 | | { |
| 3 | 111 | | return ReadOnlySpan<char>.Empty; |
| | 112 | | } |
| | 113 | |
|
| 5 | 114 | | return haystack[(pos + 1)..]; |
| | 115 | | } |
| | 116 | |
|
| | 117 | | /// <summary> |
| | 118 | | /// Returns a transliterated string which only contain ascii characters. |
| | 119 | | /// </summary> |
| | 120 | | /// <param name="text">The string to act on.</param> |
| | 121 | | /// <returns>The transliterated string.</returns> |
| | 122 | | public static string Transliterated(this string text) |
| | 123 | | { |
| 0 | 124 | | return (_transliterator.Value is null) ? text : _transliterator.Value.Transliterate(text); |
| | 125 | | } |
| | 126 | | } |
| | 127 | | } |