@@ -42,13 +42,78 @@ public static class StringExtensions
4242 /// </summary>
4343 private const DateTimeStyles DefaultStyles = DateTimeStyles . None ;
4444
45+ /// <summary>
46+ /// Obtains the zero-based index of the nth occurrence of the specified character in this instance.
47+ /// If the specified occurrence does not exist, returns -1.
48+ /// </summary>
49+ /// <param name="value">The string to search.</param>
50+ /// <param name="seekValue">The character to seek.</param>
51+ /// <param name="count">The number of occurrences to skip before returning an index.</param>
52+ /// <returns>
53+ /// Returns the zero-based index position of the nth occurrence of <paramref name="seekValue"/>, if found; otherwise, -1.
54+ /// </returns>
55+ public static int NthIndexOf ( this string value , char seekValue , int count )
56+ {
57+ if ( string . IsNullOrEmpty ( value ) || count <= 0 ) return - 1 ;
58+
59+ int occurrences = 0 ;
60+
61+ for ( int i = 0 ; i < value . Length ; i ++ )
62+ {
63+ if ( value [ i ] != seekValue ) continue ;
64+
65+ occurrences ++ ;
66+
67+ if ( occurrences != count ) continue ;
68+
69+ return i ;
70+ }
71+
72+ return NotFound ;
73+ }
74+
75+ /// <summary>
76+ /// Obtains the zero-based index of the nth occurrence of the specified character in this instance.
77+ /// If the specified occurrence does not exist, returns -1.
78+ /// </summary>
79+ /// <param name="value">The string to search.</param>
80+ /// <param name="seekValue">The substring to seek.</param>
81+ /// <param name="count">The number of occurrences to skip before returning an index.</param>
82+ /// <param name="comparison">The comparison that will be used to compare the current value and the seek value.</param>
83+ /// <returns>
84+ /// Returns the zero-based index position of the nth occurrence of <paramref name="seekValue"/>, if found; otherwise, -1.
85+ /// </returns>
86+ public static int NthIndexOf ( this string value , string seekValue , int count , StringComparison comparison = DefaultComparison )
87+ {
88+ if ( string . IsNullOrEmpty ( value ) || string . IsNullOrEmpty ( seekValue ) || count <= 0 ) return - 1 ;
89+
90+ int occurrences = 0 ;
91+ int startIndex = 0 ;
92+
93+ while ( true )
94+ {
95+ int index = value . IndexOf ( seekValue , startIndex , comparison ) ;
96+
97+ if ( index == - 1 ) return - 1 ;
98+
99+ occurrences ++ ;
100+
101+ if ( occurrences == count ) return index ;
102+
103+ startIndex = index + seekValue . Length ;
104+
105+ if ( startIndex >= value . Length ) return NotFound ;
106+ }
107+ }
108+
45109 /// <summary>
46110 /// Repeats the current <see cref="String"/> by the specified number of repetitions.
47111 /// </summary>
48112 /// <param name="value">The <see cref="String"/> instance to repeat.</param>
49113 /// <param name="count">The number of repetitions of the current <see cref="String"/> instance.</param>
50114 /// <returns>Returns a new <see cref="String"/> instance representing the repetition of the current <see cref="String"/> instance.</returns>
51- public static string Repeat ( this string value , int count ) => count > 0 ? string . Join ( string . Empty , Enumerable . Repeat ( value , count ) ) : string . Empty ;
115+ public static string Repeat ( this string value , int count ) =>
116+ count > 0 ? string . Join ( string . Empty , Enumerable . Repeat ( value , count ) ) : string . Empty ;
52117
53118 /// <summary>
54119 /// Obtains a sub-string before the specified index within the current <see cref="String"/> instance.
@@ -64,7 +129,8 @@ public static class StringExtensions
64129 /// If the default value is <see langword="null"/>, then the current <see cref="String"/> instance will be returned.
65130 /// </returns>
66131 // ReSharper disable once HeapView.ObjectAllocation
67- private static string SubstringBeforeIndex ( this string value , int index , string ? defaultValue = null ) => index <= NotFound ? defaultValue ?? value : value [ ..index ] ;
132+ private static string SubstringBeforeIndex ( this string value , int index , string ? defaultValue = null ) =>
133+ index <= NotFound ? defaultValue ?? value : value [ ..index ] ;
68134
69135 /// <summary>
70136 /// Obtains a sub-string after the specified index within the current <see cref="String"/> instance.
@@ -81,7 +147,8 @@ public static class StringExtensions
81147 /// If the default value is <see langword="null"/>, then the current <see cref="String"/> instance will be returned.
82148 /// </returns>
83149 // ReSharper disable once HeapView.ObjectAllocation
84- private static string SubstringAfterIndex ( this string value , int index , int offset , string ? defaultValue = null ) => index <= NotFound ? defaultValue ?? value : value [ ( index + offset ) ..value . Length ] ;
150+ private static string SubstringAfterIndex ( this string value , int index , int offset , string ? defaultValue = null ) =>
151+ index <= NotFound ? defaultValue ?? value : value [ ( index + offset ) ..value . Length ] ;
85152
86153 /// <summary>
87154 /// Obtains a sub-string before the first occurrence of the specified delimiter within the current <see cref="String"/> instance.
@@ -235,6 +302,95 @@ public static string SubstringAfterLast(this string value, char delimiter, strin
235302 public static string SubstringAfterLast ( this string value , string delimiter , string ? defaultValue = null , StringComparison comparison = DefaultComparison ) =>
236303 value . SubstringAfterIndex ( value . LastIndexOf ( delimiter , comparison ) , 1 , defaultValue ) ;
237304
305+ /// <summary>
306+ /// Obtains a sub-string before the nth occurrence of the specified character within the current <see cref="String"/> instance.
307+ /// If the nth occurrence is not found, returns the <paramref name="defaultValue"/> or the entire string if default is null.
308+ /// </summary>
309+ /// <param name="value">The current <see cref="String"/> instance from which to obtain a sub-string.</param>
310+ /// <param name="seekValue">The character to find the nth occurrence of.</param>
311+ /// <param name="count">The nth occurrence to find.</param>
312+ /// <param name="defaultValue">
313+ /// The <see cref="String"/> value to return if the nth occurrence is not found.
314+ /// If the default value is <see langword="null"/>, the current <see cref="String"/> instance is returned.
315+ /// </param>
316+ /// <returns>
317+ /// A sub-string before the nth occurrence of <paramref name="seekValue"/> if found; otherwise,
318+ /// <paramref name="defaultValue"/> or the entire string if default is null.
319+ /// </returns>
320+ public static string SubstringBeforeNth ( this string value , char seekValue , int count , string ? defaultValue = null )
321+ {
322+ int index = value . NthIndexOf ( seekValue , count ) ;
323+ return value . SubstringBeforeIndex ( index , defaultValue ) ;
324+ }
325+
326+ /// <summary>
327+ /// Obtains a sub-string before the nth occurrence of the specified substring within the current <see cref="String"/> instance.
328+ /// If the nth occurrence is not found, returns the <paramref name="defaultValue"/> or the entire string if default is null.
329+ /// </summary>
330+ /// <param name="value">The current <see cref="String"/> instance from which to obtain a sub-string.</param>
331+ /// <param name="seekValue">The substring to find the nth occurrence of.</param>
332+ /// <param name="count">The nth occurrence to find.</param>
333+ /// <param name="defaultValue">
334+ /// The <see cref="String"/> value to return if the nth occurrence is not found.
335+ /// If the default value is <see langword="null"/>, the current <see cref="String"/> instance is returned.
336+ /// </param>
337+ /// <param name="comparison">The comparison that will be used to compare the current value and the seek value.</param>
338+ /// <returns>
339+ /// A sub-string before the nth occurrence of <paramref name="seekValue"/> if found; otherwise,
340+ /// <paramref name="defaultValue"/> or the entire string if default is null.
341+ /// </returns>
342+ public static string SubstringBeforeNth ( this string value , string seekValue , int count , string ? defaultValue = null , StringComparison comparison = DefaultComparison )
343+ {
344+ int index = value . NthIndexOf ( seekValue , count , comparison ) ;
345+ return value . SubstringBeforeIndex ( index , defaultValue ) ;
346+ }
347+
348+ /// <summary>
349+ /// Obtains a sub-string after the nth occurrence of the specified character within the current <see cref="String"/> instance.
350+ /// If the nth occurrence is not found, returns the <paramref name="defaultValue"/> or the entire string if default is null.
351+ /// </summary>
352+ /// <param name="value">The current <see cref="String"/> instance from which to obtain a sub-string.</param>
353+ /// <param name="seekValue">The character to find the nth occurrence of.</param>
354+ /// <param name="count">The nth occurrence to find.</param>
355+ /// <param name="defaultValue">
356+ /// The <see cref="String"/> value to return if the nth occurrence is not found.
357+ /// If the default value is <see langword="null"/>, the current <see cref="String"/> instance is returned.
358+ /// </param>
359+ /// <returns>
360+ /// A sub-string after the nth occurrence of <paramref name="seekValue"/> if found; otherwise,
361+ /// <paramref name="defaultValue"/> or the entire string if default is null.
362+ /// </returns>
363+ public static string SubstringAfterNth ( this string value , char seekValue , int count , string ? defaultValue = null )
364+ {
365+ int index = value . NthIndexOf ( seekValue , count ) ;
366+ // Move 1 character after the nth occurrence index.
367+ return value . SubstringAfterIndex ( index , 1 , defaultValue ) ;
368+ }
369+
370+ /// <summary>
371+ /// Obtains a sub-string after the nth occurrence of the specified substring within the current <see cref="String"/> instance.
372+ /// If the nth occurrence is not found, returns the <paramref name="defaultValue"/> or the entire string if default is null.
373+ /// </summary>
374+ /// <param name="value">The current <see cref="String"/> instance from which to obtain a sub-string.</param>
375+ /// <param name="seekValue">The substring to find the nth occurrence of.</param>
376+ /// <param name="count">The nth occurrence to find.</param>
377+ /// <param name="defaultValue">
378+ /// The <see cref="String"/> value to return if the nth occurrence is not found.
379+ /// If the default value is <see langword="null"/>, the current <see cref="String"/> instance is returned.
380+ /// </param>
381+ /// <param name="comparison">The comparison that will be used to compare the current value and the seek value.</param>
382+ /// <returns>
383+ /// A sub-string after the nth occurrence of <paramref name="seekValue"/> if found; otherwise,
384+ /// <paramref name="defaultValue"/> or the entire string if default is null.
385+ /// </returns>
386+ public static string SubstringAfterNth ( this string value , string seekValue , int count , string ? defaultValue = null , StringComparison comparison = DefaultComparison )
387+ {
388+ int index = value . NthIndexOf ( seekValue , count , comparison ) ;
389+ // Move by the length of the found substring after the nth occurrence index.
390+ int offset = ( index != NotFound && ! string . IsNullOrEmpty ( seekValue ) ) ? seekValue . Length : 0 ;
391+ return value . SubstringAfterIndex ( index , offset , defaultValue ) ;
392+ }
393+
238394 /// <summary>
239395 /// Converts the current <see cref="String"/> instance into a new <see cref="T:Byte[]"/> instance.
240396 /// </summary>
@@ -336,7 +492,8 @@ public static bool TryCopyTo(this string value, Span<char> destination, out int
336492 /// <param name="before">The <see cref="Char"/> that should precede the current <see cref="String"/> instance.</param>
337493 /// <param name="after">The <see cref="Char"/> that should succeed the current <see cref="String"/> instance.</param>
338494 /// <returns>Returns a new <see cref="String"/> instance representing the current <see cref="String"/> instance, wrapped between the specified before and after <see cref="Char"/> instances.</returns>
339- public static string Wrap ( this string value , char before , char after ) => string . Concat ( before . ToString ( ) , value , after . ToString ( ) ) ;
495+ public static string Wrap ( this string value , char before , char after ) =>
496+ string . Concat ( before . ToString ( ) , value , after . ToString ( ) ) ;
340497
341498 /// <summary>
342499 /// Wraps the current <see cref="String"/> instance between the specified before and after <see cref="String"/> instances.
@@ -345,5 +502,6 @@ public static bool TryCopyTo(this string value, Span<char> destination, out int
345502 /// <param name="before">The <see cref="String"/> that should precede the current <see cref="String"/> instance.</param>
346503 /// <param name="after">The <see cref="String"/> that should succeed the current <see cref="String"/> instance.</param>
347504 /// <returns>Returns a new <see cref="String"/> instance representing the current <see cref="String"/> instance, wrapped between the specified before and after <see cref="String"/> instances.</returns>
348- public static string Wrap ( this string value , string before , string after ) => string . Concat ( before , value , after ) ;
505+ public static string Wrap ( this string value , string before , string after ) =>
506+ string . Concat ( before , value , after ) ;
349507}
0 commit comments