< Summary - Jellyfin

Information
Class: Jellyfin.Extensions.StringExtensions
Assembly: Jellyfin.Extensions
File(s): /srv/git/jellyfin/src/Jellyfin.Extensions/StringExtensions.cs
Line coverage
72%
Covered lines: 44
Uncovered lines: 17
Coverable lines: 61
Total lines: 214
Line coverage: 72.1%
Branch coverage
84%
Covered branches: 27
Total branches: 32
Branch coverage: 84.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/12/2026 - 12:15:57 AM Line coverage: 62.2% (28/45) Branch coverage: 77.2% (17/22) Total lines: 17710/29/2026 - 12:17:11 AM Line coverage: 72.1% (44/61) Branch coverage: 84.3% (27/32) Total lines: 214 5/12/2026 - 12:15:57 AM Line coverage: 62.2% (28/45) Branch coverage: 77.2% (17/22) Total lines: 17710/29/2026 - 12:17:11 AM Line coverage: 72.1% (44/61) Branch coverage: 84.3% (27/32) Total lines: 214

Coverage delta

Coverage delta 10 -10

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
RemoveDiacritics(...)100%11100%
HasDiacritics(...)100%22100%
Count(...)100%44100%
LeftPart(...)100%44100%
RightPart(...)100%66100%
Transliterated(...)0%620%
Trimmed(...)100%11100%
TruncateAtNull(...)0%620%
GetCleanValue(...)50%2283.33%
EscapeProcessArgument(...)100%1010100%

File(s)

/srv/git/jellyfin/src/Jellyfin.Extensions/StringExtensions.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Text;
 5using System.Text.RegularExpressions;
 6using ICU4N.Text;
 7
 8namespace Jellyfin.Extensions
 9{
 10    /// <summary>
 11    /// Provides extensions methods for <see cref="string" />.
 12    /// </summary>
 13    public static partial class StringExtensions
 14    {
 015        private static readonly Lazy<string> _transliteratorId = new(() =>
 016            Environment.GetEnvironmentVariable("JELLYFIN_TRANSLITERATOR_ID")
 017            ?? "Any-Latin; Latin-Ascii; Lower; NFD; [:Nonspacing Mark:] Remove; [:Punctuation:] Remove;");
 18
 019        private static readonly Lazy<Transliterator?> _transliterator = new(() =>
 020        {
 021            try
 022            {
 023                return Transliterator.GetInstance(_transliteratorId.Value);
 024            }
 025            catch (ArgumentException)
 026            {
 027                return null;
 028            }
 029        });
 30
 31        // Matches non-conforming unicode chars
 32        // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
 33
 34        [GeneratedRegex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(�)")]
 35        private static partial Regex NonConformingUnicodeRegex();
 36
 37        /// <summary>
 38        /// Removes the diacritics character from the strings.
 39        /// </summary>
 40        /// <param name="text">The string to act on.</param>
 41        /// <returns>The string without diacritics character.</returns>
 42        public static string RemoveDiacritics(this string text)
 42243            => Diacritics.Extensions.StringExtensions.RemoveDiacritics(
 42244                NonConformingUnicodeRegex().Replace(text, string.Empty));
 45
 46        /// <summary>
 47        /// Checks whether or not the specified string has diacritics in it.
 48        /// </summary>
 49        /// <param name="text">The string to check.</param>
 50        /// <returns>True if the string has diacritics, false otherwise.</returns>
 51        public static bool HasDiacritics(this string text)
 1252            => Diacritics.Extensions.StringExtensions.HasDiacritics(text)
 1253                || NonConformingUnicodeRegex().IsMatch(text);
 54
 55        /// <summary>
 56        /// Counts the number of occurrences of [needle] in the string.
 57        /// </summary>
 58        /// <param name="value">The haystack to search in.</param>
 59        /// <param name="needle">The character to search for.</param>
 60        /// <returns>The number of occurrences of the [needle] character.</returns>
 61        public static int Count(this ReadOnlySpan<char> value, char needle)
 62        {
 463            var count = 0;
 464            var length = value.Length;
 10865            for (var i = 0; i < length; i++)
 66            {
 5067                if (value[i] == needle)
 68                {
 669                    count++;
 70                }
 71            }
 72
 473            return count;
 74        }
 75
 76        /// <summary>
 77        /// Returns the part on the left of the <c>needle</c>.
 78        /// </summary>
 79        /// <param name="haystack">The string to seek.</param>
 80        /// <param name="needle">The needle to find.</param>
 81        /// <returns>The part left of the <paramref name="needle" />.</returns>
 82        public static ReadOnlySpan<char> LeftPart(this ReadOnlySpan<char> haystack, char needle)
 83        {
 45684            if (haystack.IsEmpty)
 85            {
 286                return ReadOnlySpan<char>.Empty;
 87            }
 88
 45489            var pos = haystack.IndexOf(needle);
 45490            return pos == -1 ? haystack : haystack[..pos];
 91        }
 92
 93        /// <summary>
 94        /// Returns the part on the right of the <c>needle</c>.
 95        /// </summary>
 96        /// <param name="haystack">The string to seek.</param>
 97        /// <param name="needle">The needle to find.</param>
 98        /// <returns>The part right of the <paramref name="needle" />.</returns>
 99        public static ReadOnlySpan<char> RightPart(this ReadOnlySpan<char> haystack, char needle)
 100        {
 36101            if (haystack.IsEmpty)
 102            {
 1103                return ReadOnlySpan<char>.Empty;
 104            }
 105
 35106            var pos = haystack.LastIndexOf(needle);
 35107            if (pos == -1)
 108            {
 1109                return haystack;
 110            }
 111
 34112            if (pos == haystack.Length - 1)
 113            {
 3114                return ReadOnlySpan<char>.Empty;
 115            }
 116
 31117            return haystack[(pos + 1)..];
 118        }
 119
 120        /// <summary>
 121        /// Returns a transliterated string which only contain ascii characters.
 122        /// </summary>
 123        /// <param name="text">The string to act on.</param>
 124        /// <returns>The transliterated string.</returns>
 125        public static string Transliterated(this string text)
 126        {
 0127            return (_transliterator.Value is null) ? text : _transliterator.Value.Transliterate(text);
 128        }
 129
 130        /// <summary>
 131        /// Ensures all strings are non-null and trimmed of leading an trailing blanks.
 132        /// </summary>
 133        /// <param name="values">The enumerable of strings to trim.</param>
 134        /// <returns>The enumeration of trimmed strings.</returns>
 135        public static IEnumerable<string> Trimmed(this IEnumerable<string?> values)
 136        {
 15137            return values.Select(i => (i ?? string.Empty).Trim());
 138        }
 139
 140        /// <summary>
 141        /// Truncates a string at the first null character ('\0').
 142        /// </summary>
 143        /// <param name="text">The input string.</param>
 144        /// <returns>
 145        /// The substring up to (but not including) the first null character,
 146        /// or the original string if no null character is present.
 147        /// </returns>
 148        public static string TruncateAtNull(this string text)
 149        {
 0150            return string.IsNullOrEmpty(text) ? text : text.AsSpan().LeftPart('\0').ToString();
 151        }
 152
 153        /// <summary>
 154        /// Normalizes a string for comparison by removing diacritics, converting to lowercase,
 155        /// replacing punctuation/special characters with spaces, and collapsing whitespace.
 156        /// </summary>
 157        /// <param name="value">The string to normalize.</param>
 158        /// <returns>The normalized string, or the original if null/whitespace.</returns>
 159        public static string GetCleanValue(this string value)
 160        {
 222161            if (string.IsNullOrWhiteSpace(value))
 162            {
 0163                return value;
 164            }
 165
 166            // Remove diacritics and convert to lowercase
 222167            var cleaned = value.RemoveDiacritics().ToLowerInvariant();
 168
 169            // Replace all punctuation and special characters with spaces
 222170            cleaned = Regex.Replace(cleaned, @"[^\p{L}\p{N}\s]", " ");
 171
 172            // Collapse multiple spaces into single space and trim
 222173            cleaned = Regex.Replace(cleaned, @"\s+", " ").Trim();
 174
 222175            return cleaned;
 176        }
 177
 178        /// <summary>
 179        /// Escapes an argument so that it survives command line parsing as a single argument when it is wrapped in doub
 180        /// </summary>
 181        /// <param name="value">The argument to escape.</param>
 182        /// <returns>The escaped argument.</returns>
 183        public static string EscapeProcessArgument(this string value)
 184        {
 15185            ArgumentNullException.ThrowIfNull(value);
 186
 15187            var span = value.AsSpan();
 15188            if (!span.Contains('"'))
 189            {
 10190                var trailing = span.Length - span.TrimEnd('\\').Length;
 10191                return trailing == 0 ? value : string.Concat(value, new string('\\', trailing));
 192            }
 193
 5194            var escaped = new StringBuilder(value.Length + 8);
 5195            var backslashes = 0;
 196
 202197            foreach (var character in span)
 198            {
 96199                if (character == '\\')
 200                {
 5201                    backslashes++;
 5202                    continue;
 203                }
 204
 91205                escaped
 91206                    .Append('\\', character == '"' ? (backslashes * 2) + 1 : backslashes)
 91207                    .Append(character);
 91208                backslashes = 0;
 209            }
 210
 5211            return escaped.Append('\\', backslashes * 2).ToString();
 212        }
 213    }
 214}