@@ -36,23 +36,33 @@ public static class StringExtensions
3636 "group" , "into" , "join" , "let" , "nameof" , "notnull" , "on" , "orderby" , "partial" , "partial" , "remove" ,
3737 "select" , "set" , "unmanaged" , "value" , "var" , "when" , "where" , "where" , "with" , "yield" , "values"
3838 } ;
39+
40+ private static readonly char [ ] EXTRA_INVALID_FILE_CHARS = { '`' } ;
41+
42+ private static readonly string [ ] WINDOWS_RESERVED_NAMES =
43+ {
44+ "CON" , "PRN" , "AUX" , "NUL" , "COM1" , "COM2" , "COM3" , "COM4" , "COM5" , "COM6" , "COM7" , "COM8" , "COM9" ,
45+ "LPT1" , "LPT2" , "LPT3" , "LPT4" , "LPT5" , "LPT6" , "LPT7" , "LPT8" , "LPT9"
46+ } ;
3947
4048 public static string Sanitize ( this string input )
4149 {
42- input = input . StartingNumbersToWords ( ) ;
43- // replace white spaces with undescore, then replace all invalid chars with empty string
44- IEnumerable < string > pascalCase =
45- INVALID_CHARS_RGX . Replace ( WHITE_SPACE . Replace ( input , "_" ) , string . Empty )
46- // split by underscores
47- . Split ( new [ ] { '_' } , StringSplitOptions . RemoveEmptyEntries )
48- // set first letter to uppercase
49- . Select ( w => STARTS_WITH_LOWER_CASE_CHAR . Replace ( w , m => m . Value . ToUpper ( ) ) )
50- // replace second and all following upper case letters to lower if there is no next lower (ABC -> Abc)
51- . Select ( w => FIRST_CHAR_FOLLOWED_BY_UPPER_CASES_ONLY . Replace ( w , m => m . Value . ToLower ( ) ) )
52- // set upper case the first lower case following a number (Ab9cd -> Ab9Cd)
53- . Select ( w => LOWER_CASE_NEXT_TO_NUMBER . Replace ( w , m => m . Value . ToUpper ( ) ) )
54- // lower second and next upper case letters except the last if it follows by any lower (ABcDEf -> AbcDef)
55- . Select ( w => UPPER_CASE_INSIDE . Replace ( w , m => m . Value . ToLower ( ) ) ) ;
50+ // Treat any non-alphanumeric sequence as a word separator
51+ input = Regex . Replace ( input , "[^a-zA-Z0-9]+" , "_" ) ;
52+
53+ IEnumerable < string > pascalCase =
54+ input
55+ . Trim ( '_' )
56+ // split by underscores
57+ . Split ( new [ ] { '_' } , StringSplitOptions . RemoveEmptyEntries )
58+ // set first letter to uppercase
59+ . Select ( w => STARTS_WITH_LOWER_CASE_CHAR . Replace ( w , m => m . Value . ToUpper ( ) ) )
60+ // replace second and all following upper case letters to lower if there is no next lower (ABC -> Abc)
61+ . Select ( w => FIRST_CHAR_FOLLOWED_BY_UPPER_CASES_ONLY . Replace ( w , m => m . Value . ToLower ( ) ) )
62+ // set upper case the first lower case following a number (Ab9cd -> Ab9Cd)
63+ . Select ( w => LOWER_CASE_NEXT_TO_NUMBER . Replace ( w , m => m . Value . ToUpper ( ) ) )
64+ // lower second and next upper case letters except the last if it follows by any lower (ABcDEf -> AbcDef)
65+ . Select ( w => UPPER_CASE_INSIDE . Replace ( w , m => m . Value . ToLower ( ) ) ) ;
5666
5767 return string . Concat ( pascalCase ) ;
5868 }
@@ -333,5 +343,55 @@ public static string GetProjectPath(this string absolutePath)
333343 string relativePath = ToPathWithConsistentSeparators ( Path . GetRelativePath ( projectPath , absolutePath ) ) ;
334344 return relativePath ;
335345 }
346+
347+ public static bool IsValidFilename ( this string value )
348+ {
349+ if ( string . IsNullOrWhiteSpace ( value ) )
350+ return false ;
351+
352+ char [ ] invalid = Path . GetInvalidFileNameChars ( ) ;
353+ if ( value . IndexOfAny ( invalid ) >= 0 )
354+ return false ;
355+
356+ if ( value . IndexOfAny ( EXTRA_INVALID_FILE_CHARS ) >= 0 )
357+ return false ;
358+
359+ if ( value . EndsWith ( " " ) || value . EndsWith ( "." ) )
360+ return false ;
361+
362+ string stem = Path . GetFileNameWithoutExtension ( value ) ;
363+ if ( WINDOWS_RESERVED_NAMES . Any ( r => r . Equals ( stem , StringComparison . OrdinalIgnoreCase ) ) )
364+ return false ;
365+
366+ return true ;
367+ }
368+
369+ public static string ToValidFilename ( this string value , char replacement = '_' )
370+ {
371+ if ( string . IsNullOrEmpty ( value ) )
372+ return value ;
373+
374+ if ( value . IsValidFilename ( ) )
375+ return value ;
376+
377+ string safe = value ;
378+
379+ foreach ( char c in Path . GetInvalidFileNameChars ( ) )
380+ safe = safe . Replace ( c , replacement ) ;
381+
382+ foreach ( char c in EXTRA_INVALID_FILE_CHARS )
383+ safe = safe . Replace ( c , replacement ) ;
384+
385+ safe = safe . TrimEnd ( ' ' , '.' ) ;
386+
387+ string stem = Path . GetFileNameWithoutExtension ( safe ) ;
388+ if ( WINDOWS_RESERVED_NAMES . Any ( r => r . Equals ( stem , StringComparison . OrdinalIgnoreCase ) ) )
389+ safe = "_" + safe ;
390+
391+ if ( string . IsNullOrWhiteSpace ( safe ) )
392+ safe = "_" ;
393+
394+ return safe ;
395+ }
336396 }
337397}
0 commit comments