-
-
Notifications
You must be signed in to change notification settings - Fork 345
/
StringExtensions.cs
1732 lines (1536 loc) · 63.8 KB
/
StringExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// <copyright>
// Copyright by the Spark Development Network
//
// Licensed under the Rock Community License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.rockrms.com/license
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace Rock
{
/// <summary>
/// Handy string extensions that don't require any NuGet packages or Rock references
/// </summary>
public static class StringExtensions
{
#region String Extensions
/// <summary>
/// Prepends a character to a string if it doesn't already exist.
/// </summary>
/// <param name="text"></param>
/// <param name="prepend"></param>
/// <returns></returns>
public static string AddStringAtBeginningIfItDoesNotExist( this string text, string prepend )
{
if ( text == null )
return prepend;
if ( prepend == null )
prepend = "";
return text.StartsWith( prepend ) ? text : prepend + text;
}
/// <summary>
/// Gets the nth occurrence of a string within a string. Pass 0 for the first occurrence, 1 for the second.
/// </summary>
/// <param name="str"></param>
/// <param name="value"></param>
/// <param name="nth"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static int IndexOfNth( this string str, string value, int nth = 0, StringComparison comparisonType = StringComparison.OrdinalIgnoreCase )
{
if ( nth < 0 )
throw new ArgumentException( "Can not find a negative index of substring in string. Must start with 0" );
int offset = str.IndexOf( value, comparisonType );
for ( int i = 0; i < nth; i++ )
{
if ( offset == -1 )
return -1;
offset = str.IndexOf( value, offset + 1, comparisonType );
}
return offset;
}
/// <summary>
/// Converts string to MD5 hash
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
public static string Md5Hash( this string str )
{
using ( var crypt = MD5.Create() )
{
var hash = crypt.ComputeHash( Encoding.UTF8.GetBytes( str ) );
StringBuilder sb = new StringBuilder();
foreach ( byte b in hash )
{
// Can be "x2" if you want lowercase
sb.Append( b.ToString( "x2" ) );
}
return sb.ToString();
}
}
/// <summary>
/// Converts string to Sha1 hash
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
public static string Sha1Hash( this string str )
{
using ( var crypt = new SHA1Managed() )
{
var hash = crypt.ComputeHash( Encoding.UTF8.GetBytes( str ) );
var sb = new StringBuilder( hash.Length * 2 );
foreach ( byte b in hash )
{
// Can be "x2" if you want lowercase
sb.Append( b.ToString( "x2" ) );
}
return sb.ToString();
}
}
/// <summary>
/// Converts string to Sha256 hash
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
public static string Sha256Hash( this string str )
{
using ( var crypt = new System.Security.Cryptography.SHA256Managed() )
{
var hash = crypt.ComputeHash( Encoding.UTF8.GetBytes( str ) );
var sb = new StringBuilder();
foreach ( byte b in hash )
{
// Can be "x2" if you want lowercase
sb.Append( b.ToString( "x2" ) );
}
return sb.ToString();
}
}
/// <summary>
/// Converts string to HMAC_SHA1 string using key
/// </summary>
/// <param name="str">The string.</param>
/// <param name="keyString">The key.</param>
/// <returns></returns>
public static string HmacSha1Hash( this string str, string keyString )
{
var key = Encoding.ASCII.GetBytes( keyString );
using ( var crypt = new HMACSHA1( key ) )
{
var hash = crypt.ComputeHash( Encoding.ASCII.GetBytes( str ) );
// Can be "x2" if you want lowercase
return hash.Aggregate( "", ( s, e ) => s + String.Format( "{0:x2}", e ), s => s );
}
}
/// <summary>
/// Converts string to HMAC_SHA256 string using key
/// </summary>
/// <param name="str">The string.</param>
/// <param name="keyString">The key string.</param>
/// <returns></returns>
public static string HmacSha256Hash( this string str, string keyString )
{
var key = Encoding.ASCII.GetBytes( keyString );
using ( var crypt = new HMACSHA256( key ) )
{
var hash = crypt.ComputeHash( Encoding.ASCII.GetBytes( str ) );
// Can be "x2" if you want lowercase
return hash.Aggregate( "", ( s, e ) => s + String.Format( "{0:x2}", e ), s => s );
}
}
/// <summary>
/// Reads the parameter to check for DOM objects and possible URLs
/// Accepts an encoded string and returns an encoded string
/// </summary>
/// <param name="encodedString"></param>
[RockObsolete( "1.16.1" )]
[Obsolete( "This method is no longer suitable. Consider using RedirectUrlContainsXss." )]
public static string ScrubEncodedStringForXSSObjects( this string encodedString )
{
var decodedString = encodedString.GetFullyUrlDecodedValue();
if ( decodedString.HasXssObjects() )
{
return "%2f";
}
return encodedString;
}
/// <summary>
/// Determines whether <paramref name="decodedString"/> has XSS objects.
/// </summary>
/// <param name="decodedString">The decoded string.</param>
/// <returns>
/// <c>true</c> if <paramref name="decodedString"/> has XSS objects; otherwise, <c>false</c>.
/// </returns>
[RockObsolete( "1.16.1" )]
[Obsolete( "This method is no longer suitable. Consider using RedirectUrlContainsXss." )]
public static bool HasXssObjects( this string decodedString )
{
// Characters used by DOM Objects; javascript, document, window and URLs
char[] badCharacters = new char[] { '<', '>', ':', '*' };
if ( decodedString?.IndexOfAny( badCharacters ) >= 0 )
{
return true;
}
return false;
}
/// <summary>
/// Checks a value intended to be a redirect URL for XSS injection methods.
/// </summary>
/// <param name="redirectUrl">The value intended to be used for a redirect URL.</param>
/// <returns>
/// <c>true</c> if [redirectUrl contains XSS injection methods]; otherwise, <c>false</c>.
/// </returns>
public static bool RedirectUrlContainsXss( this string redirectUrl )
{
if ( string.IsNullOrWhiteSpace( redirectUrl ) )
{
return false; // Early exit, nothing to check.
}
// These are characters which we will not ever allow in a URL query string value. We used to block colon (:), but that
// has a valid use in DateTime values.
char[] badCharacters = new char[] { '<', '>', '*' };
// Ensure we URL and HTML decode all characters, even if they are double/triple/etc encoded.
var decodedString = redirectUrl.GetFullyUrlDecodedValue().GetFullyHtmlDecodedValue();
// Check for bad characters.
if ( decodedString.IndexOfAny( badCharacters ) >= 0 )
{
return true;
}
// Some special whitespace characters are ignored by the browser when parsing script. Remove all whitespace from the
// string and make it lower case for easier comparison.
decodedString = Regex.Replace( decodedString, @"\s+", "" ).ToLower();
// These are strings that should never be allowed in a URL query string value.
string[] blockedStrings = new string[] { "javascript:" };
// Check for bad strings.
foreach ( string blockedString in blockedStrings )
{
if ( decodedString.Contains( blockedString ) )
{
return true;
}
}
// Everything looks okay.
return false;
}
/// <summary>
/// Gets a fully URL-decoded string (or returns string.Empty if it cannot be decoded within 10 attempts).
/// </summary>
/// <param name="encodedString">The encoded string.</param>
/// <returns></returns>
public static string GetFullyUrlDecodedValue( this string encodedString )
{
if ( string.IsNullOrWhiteSpace( encodedString) )
{
return encodedString;
}
int loopCount = 0;
var decodedString = encodedString;
var testString = WebUtility.UrlDecode( encodedString );
while ( testString != decodedString )
{
loopCount++;
if ( loopCount >= 10 )
{
return string.Empty;
}
decodedString = testString;
testString = WebUtility.UrlDecode( testString );
}
return decodedString;
}
/// <summary>
/// Gets a fully HTML-decoded string (or returns string.Empty if it cannot be decoded within 10 attempts).
/// </summary>
/// <param name="encodedString">The encoded string.</param>
/// <returns></returns>
public static string GetFullyHtmlDecodedValue( this string encodedString )
{
if ( string.IsNullOrWhiteSpace( encodedString ) )
{
return encodedString;
}
int loopCount = 0;
var decodedString = encodedString;
var testString = HtmlDecodeCharactersWithoutSeparators( encodedString );
while ( testString != decodedString )
{
loopCount++;
if ( loopCount >= 10 )
{
return string.Empty;
}
decodedString = testString;
testString = HtmlDecodeCharactersWithoutSeparators( testString );
}
return decodedString;
}
/// <summary>
/// This method inserts missing semi-colon separators into HTML-encoded character strings that use hex or decimal character
/// references before HTML decoding them. Browsers will render these strings without the separators, but
/// <see cref="WebUtility.HtmlDecode"/> will not.
/// </summary>
/// <param name="encodedString">The encoded string.</param>
/// <returns></returns>
private static string HtmlDecodeCharactersWithoutSeparators( this string encodedString )
{
if ( string.IsNullOrWhiteSpace( encodedString ) )
{
return encodedString;
}
// Hex encoded HTML characters should follow the format "�" (for decimal) or � (for hex). If we don't have
// an ampersand together with a pound symbol, we can just use the base method.
if ( !encodedString.Contains( "&#" ) )
{
return WebUtility.HtmlDecode( encodedString );
}
// This loop will shift each segment of the string from encodedString to correctedEncodedString, adding any missing
// semi-colons after each decimal or hex encoded character.
var correctedEncodedString = string.Empty;
while ( encodedString.Contains( "&#" ) )
{
var elementBoundary = encodedString.IndexOf( "&#" );
// Start by putting everything before the next &# into the corrected string and removing it from the original.
correctedEncodedString += encodedString.Substring( 0, elementBoundary ) + "&#";
elementBoundary += 2;
encodedString = encodedString.Substring( elementBoundary, encodedString.Length - elementBoundary );
// If the next character in the string is an "x", move it to the corrected string.
var nextChar = encodedString.FirstOrDefault();
bool useHex = false;
if ( nextChar == 'x' )
{
useHex = true;
correctedEncodedString += nextChar;
encodedString = encodedString.Substring( 1, encodedString.Length - 1 );
nextChar = encodedString.FirstOrDefault();
}
// Keep moving any digits to the corrected string. There's technically no limit on padding zeros.
var isDigit = useHex ? nextChar.IsHexDigit() : char.IsDigit( nextChar );
while ( isDigit )
{
correctedEncodedString += nextChar;
encodedString = encodedString.Substring( 1, encodedString.Length - 1 );
nextChar = encodedString.FirstOrDefault();
isDigit = useHex ? nextChar.IsHexDigit() : char.IsDigit( nextChar );
}
// Add missing semi-colons.
if ( nextChar != ';' )
{
correctedEncodedString += ";";
}
}
// Re-append any trailing characters left over in the original string.
correctedEncodedString += encodedString;
return WebUtility.HtmlDecode( correctedEncodedString );
}
/// <summary>
/// Checks a digit to see if it's a valid hexadecimal character (0-9 or A-F). Permits lowercase.
/// </summary>
/// <param name="c">The character.</param>
/// <returns></returns>
private static bool IsHexDigit( this char c )
{
char[] nonNumericHexCharacters = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f' };
return char.IsDigit( c ) || nonNumericHexCharacters.Contains( c );
}
/// <summary>
/// Joins and array of strings using the provided separator.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="separator">The separator.</param>
/// <returns>Concatenated string.</returns>
public static string JoinStrings( this IEnumerable<string> source, string separator )
{
return string.Join( separator, source.ToArray() );
}
/// <summary>
/// Joins an array of English strings together with commas plus "and" for last element.
/// </summary>
/// <param name="source">The source.</param>
/// <returns>Concatenated string.</returns>
public static string JoinStringsWithCommaAnd( this IEnumerable<String> source )
{
if ( source == null || source.Count() == 0 )
{
return string.Empty;
}
var output = string.Empty;
var list = source.ToList();
if ( list.Count > 1 )
{
var delimited = string.Join( ", ", list.Take( list.Count - 1 ) );
output = string.Concat( delimited, " and ", list.LastOrDefault() );
}
else
{
// only one element, just use it
output = list[0];
}
return output;
}
/// <summary>
/// Joins an array of English strings together with a chosen delimiter, plus a final delimiter for last element, with a maximum length of results and truncation value.
/// </summary>
/// <param name="source"></param>
/// <param name="repeatDelimiter">The delimiter for all but the final string.</param>
/// <param name="finalDelimiter">The delimiter for only the final string.</param>
/// <param name="maxLength">The maximum length allowed for the concatenated string from the source (not including the <paramref name="truncation"/> string).</param>
/// <param name="truncation">The truncation string value (default is "..." for ellipsis).</param>
/// <returns>Concatenated string.</returns>
public static string JoinStringsWithRepeatAndFinalDelimiterWithMaxLength( this IEnumerable<String> source, string repeatDelimiter, string finalDelimiter, int? maxLength, string truncation = "..." )
{
if ( source == null || source.Count() == 0 )
{
return string.Empty;
}
var output = string.Empty;
var list = source.ToList();
if ( list.Count > 1 )
{
var delimited = string.Join( repeatDelimiter, list.Take( list.Count - 1 ) );
output = string.Concat( delimited, finalDelimiter, list.LastOrDefault() );
}
else
{
// only one element, just use it.
output = list[0];
}
if ( maxLength.HasValue && output.Length > maxLength.Value )
{
output = output.Substring( 0, maxLength.Value ) + truncation;
}
return output;
}
/// <summary>
/// Removes special characters from the string so that only Alpha, Numeric, '.' and '_' remain;
/// </summary>
/// <param name="str">The identifier.</param>
/// <returns></returns>
public static string RemoveSpecialCharacters( this string str )
{
StringBuilder sb = new StringBuilder();
foreach ( char c in str )
{
if ( ( c >= '0' && c <= '9' ) || ( c >= 'A' && c <= 'Z' ) || ( c >= 'a' && c <= 'z' ) || c == '.' || c == '_' )
{
sb.Append( c );
}
}
return sb.ToString();
}
/// <summary>
/// Replaces the special characters from the string with the supplied string so that only alpha-numeric, '.', and '_' remain.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="replacementCharacters">The characters to replace special character(s) with. No restrictions or validation.</param>
/// <returns></returns>
public static string ReplaceSpecialCharacters( this string str, string replacementCharacters )
{
StringBuilder sb = new StringBuilder();
foreach ( char c in str )
{
if ( ( c >= '0' && c <= '9' ) || ( c >= 'A' && c <= 'Z' ) || ( c >= 'a' && c <= 'z' ) || c == '.' || c == '_' )
{
sb.Append( c );
}
else
{
sb.Append( replacementCharacters );
}
}
return sb.ToString();
}
/// <summary>
/// Removes all non alpha numeric characters from a string
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
public static string RemoveAllNonAlphaNumericCharacters( this string str )
{
return string.Concat( str.Where( c => char.IsLetterOrDigit( c ) ) );
}
/// <summary>
/// Removes all non numeric characters.
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
public static string RemoveAllNonNumericCharacters( this string str )
{
Regex digitsOnly = new Regex( @"[^\d]" );
if ( !string.IsNullOrEmpty( str ) )
{
return digitsOnly.Replace( str, string.Empty );
}
else
{
return string.Empty;
}
}
/// <summary>
/// Determines whether the string is not null or whitespace.
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
[System.Diagnostics.DebuggerStepThrough]
public static bool IsNotNullOrWhiteSpace( this string str )
{
return !string.IsNullOrWhiteSpace( str );
}
/// <summary>
/// Determines whether [is null or white space].
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
[System.Diagnostics.DebuggerStepThrough]
public static bool IsNullOrWhiteSpace( this string str )
{
return string.IsNullOrWhiteSpace( str );
}
/// <summary>
/// Returns the right most part of a string of the given length.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="length">The length.</param>
/// <returns></returns>
public static string Right( this string str, int length )
{
return str.SubstringSafe( str.Length - length );
}
/// <summary>
/// Strips HTML from the string.
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
public static string StripHtml( this string str )
{
return str.IsNullOrWhiteSpace()
? str
: Regex.Replace( str, @"<.*?>|<!--(.|\r|\n)*?-->", string.Empty );
}
/// <summary>
/// Determines whether the string is made up of only digits
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
public static bool IsDigitsOnly( this string str )
{
foreach ( char c in str )
{
if ( c < '0' || c > '9' )
{
return false;
}
}
return true;
}
/// <summary>
/// Returns the number of words in the string.
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
public static int WordCount( this string str )
{
// Attribution (aka future blame): https://stackoverflow.com/questions/8784517/counting-number-of-words-in-c-sharp
char[] delimiters = new char[] { ' ', '\r', '\n' };
return str.Split( delimiters, StringSplitOptions.RemoveEmptyEntries ).Length;
}
/// <summary>
/// Determines whether the string is valid mac address.
/// Works with colons, dashes, or no seperators
/// </summary>
/// <param name="str">The string.</param>
/// <returns>
/// <c>true</c> if valid mac address otherwise, <c>false</c>.
/// </returns>
public static bool IsValidMacAddress( this string str )
{
Regex regex = new Regex( "^([0-9a-fA-F]{2}(?:[:-]?[0-9a-fA-F]{2}){5})$" );
return regex.IsMatch( str );
}
/// <summary>
/// Determines whether the string is a valid http(s) URL
/// </summary>
/// <param name="str">The string.</param>
/// <returns>
/// <c>true</c> if [is valid URL] [the specified string]; otherwise, <c>false</c>.
/// </returns>
public static bool IsValidUrl( this string str )
{
Uri uriResult;
return Uri.TryCreate( str, UriKind.Absolute, out uriResult )
&& ( uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps );
}
/// <summary>
/// Removes invalid, reserved, and unreccommended characters from strings that will be used in URLs.
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
public static string RemoveInvalidReservedUrlChars( this string str )
{
return str.Replace( " ", string.Empty )
.Replace( ";", string.Empty )
.Replace( "/", string.Empty )
.Replace( "?", string.Empty )
.Replace( ":", string.Empty )
.Replace( "@", string.Empty )
.Replace( "=", string.Empty )
.Replace( "&", string.Empty )
.Replace( "<", string.Empty )
.Replace( ">", string.Empty )
.Replace( "#", string.Empty )
.Replace( "%", string.Empty )
.Replace( "\"", string.Empty )
.Replace( "{", string.Empty )
.Replace( "}", string.Empty )
.Replace( "|", string.Empty )
.Replace( "\\", string.Empty )
.Replace( "^", string.Empty )
.Replace( "[", string.Empty )
.Replace( "]", string.Empty )
.Replace( "`", string.Empty )
.Replace( "'", string.Empty );
}
/// <summary>
/// Converts a comma delimited string into a List<int>
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
public static IEnumerable<int> StringToIntList( this string str )
{
// https://stackoverflow.com/questions/1763613/convert-comma-separated-string-of-ints-to-int-array
if ( String.IsNullOrEmpty( str ) )
{
yield break;
}
foreach ( var s in str.Split( ',' ) )
{
int num;
if ( int.TryParse( s, out num ) )
{
yield return num;
}
}
}
/// <summary>
/// Makes the Int64 hash code from the provided string.
/// </summary>
/// <param name="str">The string.</param>
/// <returns></returns>
public static long MakeInt64HashCode( this string str )
{
// http://www.codeproject.com/Articles/34309/Convert-String-to-64bit-Integer
long hashCode = 0;
if ( !string.IsNullOrEmpty( str ) )
{
// Unicode Encode Covering all characterset
byte[] byteContents = Encoding.Unicode.GetBytes( str );
System.Security.Cryptography.SHA256 hash =
new System.Security.Cryptography.SHA256CryptoServiceProvider();
byte[] hashText = hash.ComputeHash( byteContents );
long hashCodeStart = BitConverter.ToInt64( hashText, 0 );
long hashCodeMedium = BitConverter.ToInt64( hashText, 8 );
long hashCodeEnd = BitConverter.ToInt64( hashText, 24 );
hashCode = hashCodeStart ^ hashCodeMedium ^ hashCodeEnd;
}
return hashCode;
}
/// <summary>
/// removes any invalid FileName chars in a filename
/// from http://stackoverflow.com/a/14836763/1755417
/// </summary>
/// <param name="name">The name.</param>
/// <returns></returns>
public static string MakeValidFileName( this string name )
{
string invalidChars = Regex.Escape( new string( System.IO.Path.GetInvalidFileNameChars() ) );
string invalidReStr = string.Format( @"[{0}]+", invalidChars );
string replace = Regex.Replace( name, invalidReStr, "_" ).Replace( ";", string.Empty ).Replace( ",", string.Empty );
return replace;
}
/// <summary>
/// Splits a Camel or Pascal cased identifier into separate words.
/// </summary>
/// <param name="str">The identifier.</param>
/// <returns></returns>
public static string SplitCase( this string str )
{
if ( str == null )
{
return null;
}
return Regex.Replace( Regex.Replace( str, @"(\P{Ll})(\P{Ll}\p{Ll})", "$1 $2" ), @"(\p{Ll})(\P{Ll})", "$1 $2" );
}
/// <summary>
/// Returns a string array that contains the substrings in this string that are delimited by any combination of whitespace, comma, semi-colon, or pipe characters.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="whitespace">if set to <c>true</c> whitespace will be treated as a delimiter</param>
/// <returns></returns>
public static string[] SplitDelimitedValues( this string str, bool whitespace = true )
{
if ( str == null )
{
return new string[0];
}
string regex = whitespace ? @"[\s\|,;]+" : @"[\|,;]+";
char[] delimiter = new char[] { ',' };
return Regex.Replace( str, regex, "," ).Split( delimiter, StringSplitOptions.RemoveEmptyEntries );
}
/// <summary>
/// Returns an array that contains substrings of the target string that are separated by the specified delimiter.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="delimiter">The delimiter string.</param>
/// <returns></returns>
public static string[] SplitDelimitedValues( this string str, string delimiter )
{
return SplitDelimitedValues( str, delimiter, StringSplitOptions.None );
}
/// <summary>
/// Returns an array that contains substrings of the target string that are separated by the specified delimiter.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="delimiter">The delimiter string.</param>
/// <param name="options">The split options.</param>
/// <returns></returns>
public static string[] SplitDelimitedValues( this string str, string delimiter, StringSplitOptions options )
{
if ( str == null )
{
return new string[0];
}
// Replace the custom delimiter string with a single unprintable character that will not appear in the target string, then use the default string split function.
var newDelimiter = new char[] { '\x0001' };
var replaceString = str.Replace( delimiter, new string( newDelimiter ) )
.Split( newDelimiter, options );
return replaceString;
}
/// <summary>
/// Replaces every instance of oldValue (regardless of case) with the newValue.
/// (from http://www.codeproject.com/Articles/10890/Fastest-C-Case-Insenstive-String-Replace)
/// </summary>
/// <param name="str">The source string.</param>
/// <param name="oldValue">The value to replace.</param>
/// <param name="newValue">The value to insert.</param>
/// <returns></returns>
public static string ReplaceCaseInsensitive( this string str, string oldValue, string newValue )
{
if ( str == null )
{
return null;
}
int count, position0, position1;
count = position0 = position1 = 0;
string upperString = str.ToUpper();
string upperPattern = oldValue.ToUpper();
int inc = ( str.Length / oldValue.Length ) *
( newValue.Length - oldValue.Length );
char[] chars = new char[str.Length + Math.Max( 0, inc )];
while ( ( position1 = upperString.IndexOf( upperPattern, position0 ) ) != -1 )
{
for ( int i = position0; i < position1; ++i )
{
chars[count++] = str[i];
}
for ( int i = 0; i < newValue.Length; ++i )
{
chars[count++] = newValue[i];
}
position0 = position1 + oldValue.Length;
}
if ( position0 == 0 )
{
return str;
}
for ( int i = position0; i < str.Length; ++i )
{
chars[count++] = str[i];
}
return new string( chars, 0, count );
}
/// <summary>
/// Replaces every instance of oldValue with newValue. Will continue to replace
/// values after each replace until the oldValue does not exist.
/// </summary>
/// <param name="str">The source string.</param>
/// <param name="oldValue">The value to replace.</param>
/// <param name="newValue">The value to insert.</param>
/// <returns>System.String.</returns>
public static string ReplaceWhileExists( this string str, string oldValue, string newValue )
{
string newstr = str;
if ( oldValue != newValue )
{
while ( newstr.Contains( oldValue ) )
{
newstr = newstr.Replace( oldValue, newValue );
}
}
return newstr;
}
/// <summary>
/// Adds escape character for quotes in a string.
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string EscapeQuotes( this string str )
{
if ( str == null )
{
return null;
}
return str.Replace( "'", "\\'" ).Replace( "\"", "\\\"" );
}
/// <summary>
/// Standardize quotes in a string. It replaces curly single quotes into the standard single quote character (ASCII 39).
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string StandardizeQuotes( this string str )
{
if ( str == null )
{
return null;
}
return str.Replace( "’", "'" );
}
/// <summary>
/// Adds Quotes around the specified string and escapes any quotes that are already in the string.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="quoteChar">The quote character.</param>
/// <returns></returns>
public static string Quoted( this string str, string quoteChar = "'" )
{
var result = quoteChar + str.EscapeQuotes() + quoteChar;
return result;
}
/// <summary>
/// Returns the specified number of characters, starting at the left side of the string.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="length">The desired length.</param>
/// <returns></returns>
public static string Left( this string str, int length )
{
return str.SubstringSafe( 0, length );
}
/// <summary>
/// Truncates from char 0 to the length and then add an ellipsis character char 8230.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="length">The length.</param>
/// <returns></returns>
public static string LeftWithEllipsis( this string str, int length )
{
if ( str.Length <= length )
{
return str;
}
return Left( str, length ) + ( char ) 8230;
}
/// <summary>
/// Returns a substring of a string. Uses an empty string for any part that doesn't exist and will return a partial substring if the string isn't long enough for the requested length (the built-in method would throw an exception in these cases).
/// </summary>
/// <param name="str">The string.</param>
/// <param name="startIndex">The 0-based starting position.</param>
/// <param name="maxLength">The maximum length.</param>
/// <returns></returns>
[Obsolete( "Use SubstringSafe() instead. Obsolete as of 1.12.0" )]
[RockObsolete( "1.12" )]
public static string SafeSubstring( this string str, int startIndex, int maxLength )
{
return str.SubstringSafe( startIndex, maxLength );
}
/// <summary>
/// Returns a substring of a string. Uses an empty string for any part that doesn't exist and will return a partial substring if the string isn't long enough for the requested length (the built-in method would throw an exception in these cases).
/// </summary>
/// <param name="str">The string.</param>
/// <param name="startIndex">The 0-based starting position.</param>
/// <param name="maxLength">The maximum length.</param>
/// <returns></returns>
public static string SubstringSafe( this string str, int startIndex, int maxLength )
{
if ( str == null || maxLength < 0 || startIndex < 0 || startIndex > str.Length )
{
return string.Empty;
}
return str.Substring( startIndex, Math.Min( maxLength, str.Length - startIndex ) );
}
/// <summary>
/// Returns a substring of a string. Uses an empty string for any part that doesn't exist and will return a partial substring if the string isn't long enough for the requested length (the built-in method would throw an exception in these cases).
/// </summary>
/// <param name="str">The string.</param>