Rob van der Woude's Scripting Pages
Powered by GeSHi

Source code for rxreplace.cs

(view source code of rxreplace.cs as plain text)

  1. using System;
  2. using System.IO;
  3. using System.Runtime.InteropServices;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6.  
  7.  
  8. namespace RobvanderWoude
  9. {
  10. 	class RxReplace
  11. 	{
  12. 		static string progver = "1.01";
  13.  
  14.  
  15. 		static int Main( string[] args )
  16. 		{
  17. 			#region Initialize variables
  18.  
  19. 			string filename = string.Empty;
  20. 			string input = string.Empty;
  21. 			string pattern = string.Empty;
  22. 			string replacement = String.Empty;
  23. 			string result = String.Empty;
  24. 			string targetfile = String.Empty;
  25. 			string[] options = null;
  26. 			RegexOptions regexoptions = RegexOptions.None;
  27. 			Encoding encoding = Encoding.Default;
  28. 			bool errorredirected = ConsoleEx.ErrorRedirected;
  29. 			bool inputredirected = ConsoleEx.InputRedirected;
  30. 			bool outputredirected = ConsoleEx.OutputRedirected;
  31. 			bool caseset = false;
  32. 			bool outputset = false;
  33. 			bool skipset = false;
  34. 			bool takeset = false;
  35. 			int redirectnum = ( inputredirected ? 1 : 0 );
  36. 			int searchbytes = -1;
  37. 			int skipmatches = 0;
  38. 			int takematches = 0;
  39.  
  40. 			#endregion Initialize variables
  41.  
  42.  
  43. 			try
  44. 			{
  45. 				#region Command Line Parsing
  46.  
  47. 				if ( args.Length == 0 )
  48. 				{
  49. 					return WriteError( );
  50. 				}
  51.  
  52. 				// Check for /? on the command line
  53. 				foreach ( string arg in args )
  54. 				{
  55. 					if ( arg == "/?" )
  56. 					{
  57. 						return WriteError( );
  58. 					}
  59. 				}
  60.  
  61. 				// Check and interpret command line arguments
  62. 				if ( args.Length + redirectnum == 3 )
  63. 				{
  64. 					if ( inputredirected )
  65. 					{
  66. 						pattern = args[0];
  67. 						replacement = args[1];
  68. 					}
  69. 					else
  70. 					{
  71. 						filename = args[0];
  72. 						pattern = args[1];
  73. 						replacement = args[2];
  74. 					}
  75. 				}
  76. 				else if ( args.Length + redirectnum > 7 )
  77. 				{
  78. 					return WriteError( );
  79. 				}
  80. 				else
  81. 				{
  82. 					if ( inputredirected )
  83. 					{
  84. 						pattern = args[0];
  85. 						replacement = args[1];
  86. 						options = args.Slice( 2, args.Length );
  87. 					}
  88. 					else
  89. 					{
  90. 						filename = args[0];
  91. 						pattern = args[1];
  92. 						replacement = args[2];
  93. 						options = args.Slice( 3, args.Length );
  94. 					}
  95. 				}
  96.  
  97. 				if ( options != null )
  98. 				{
  99. 					foreach ( string option in options )
  100. 					{
  101. 						switch ( option.ToUpper( ).Substring( 0, 2 ) )
  102. 						{
  103. 							case "/F":
  104. 								if ( searchbytes != -1 )
  105. 								{
  106. 									return WriteError( "Duplicate command line switch /F" );
  107. 								}
  108. 								try
  109. 								{
  110. 									searchbytes = Convert.ToInt32( option.Substring( 3 ) );
  111. 								}
  112. 								catch ( Exception )
  113. 								{
  114. 									return WriteError( String.Format( "Invalid command line switch {0}", option ) );
  115. 								}
  116. 								break;
  117. 							case "/I":
  118. 								if ( caseset )
  119. 								{
  120. 									return WriteError( "Duplicate command line switch /I" );
  121. 								}
  122. 								regexoptions |= RegexOptions.IgnoreCase;
  123. 								caseset = true;
  124. 								break;
  125. 							case "/O":
  126. 								if ( outputset )
  127. 								{
  128. 									return WriteError( "Duplicate switch /O" );
  129. 								}
  130. 								if ( outputredirected )
  131. 								{
  132. 									return WriteError( "Cannot combine command line switch /O and output redirection" );
  133. 								}
  134. 								if ( option.Trim( '"' ).Length > 4 && option[2] == ':' )
  135. 								{
  136. 									targetfile = option.Substring( 3 );
  137. 									if ( !Directory.GetParent( targetfile ).Exists )
  138. 									{
  139. 										return WriteError( String.Format( "Invalid output path \"{0}\"", targetfile ) );
  140. 									}
  141. 								}
  142. 								else if ( !inputredirected )
  143. 								{
  144. 									targetfile = filename;
  145. 								}
  146. 								else
  147. 								{
  148. 									return WriteError( "Output file name is mandatory when combining command line switch /O and input redirection" );
  149. 								}
  150. 								outputset = true;
  151. 								break;
  152. 							case "/S":
  153. 								if ( skipset )
  154. 								{
  155. 									return WriteError( "Duplicate command line switch /S" );
  156. 								}
  157. 								try
  158. 								{
  159. 									skipmatches = Convert.ToInt32( option.Substring( 3 ) );
  160. 								}
  161. 								catch ( Exception e )
  162. 								{
  163. 									Console.Error.WriteLine( "Error: {0}", e.Message );
  164. 									return WriteError( String.Format( "Invalid command line argument \"{0}\"", option ) );
  165. 								}
  166. 								skipset = true;
  167. 								break;
  168. 							case "/T":
  169. 								if ( takeset )
  170. 								{
  171. 									return WriteError( "Duplicate switch /T" );
  172. 								}
  173. 								try
  174. 								{
  175. 									takematches = Convert.ToInt32( option.Substring( 3 ) );
  176. 								}
  177. 								catch ( Exception e )
  178. 								{
  179. 									Console.Error.WriteLine( "Error: {0}", e.Message );
  180. 									return WriteError( String.Format( "Invalid command line argument \"{0}\"", option ) );
  181. 								}
  182. 								takeset = true;
  183. 								break;
  184. 							default:
  185. 								return WriteError( String.Format( "Invalid command line {0}: \"{1}\"", ( option[0] == '/' ? "switch" : "argument" ), option ) );
  186. 						}
  187. 					}
  188. 				}
  189.  
  190. 				#endregion Command Line Parsing
  191.  
  192.  
  193. 				#region Read input
  194.  
  195. 				if ( inputredirected )
  196. 				{
  197. 					// Read the redirected Standard Input
  198. 					input = Console.In.ReadToEnd( );
  199. 					if ( searchbytes > -1 && searchbytes < input.Length )
  200. 					{
  201. 						input = input.Substring( 0, searchbytes );
  202. 					}
  203. 				}
  204. 				else
  205. 				{
  206. 					// Check if the file name is valid
  207. 					if ( filename.IndexOf( "/" ) > -1 )
  208. 					{
  209. 						return WriteError( );
  210. 					}
  211. 					if ( filename.IndexOfAny( "?*".ToCharArray( ) ) > -1 )
  212. 					{
  213. 						return WriteError( "Wildcards not allowed" );
  214. 					}
  215. 					// Check if the file exists
  216. 					if ( File.Exists( filename ) )
  217. 					{
  218. 						// Get file size
  219. 						long filesize = new FileInfo( filename ).Length;
  220. 						// Read the file content
  221. 						using ( StreamReader file = new StreamReader( filename ) )
  222. 						{
  223. 							if ( searchbytes > -1 && searchbytes < filesize )
  224. 							{
  225. 								char[] buffer = new char[searchbytes];
  226. 								file.Read( buffer, 0, searchbytes );
  227. 								input = String.Join( String.Empty, buffer );
  228. 							}
  229. 							else
  230. 							{
  231. 								input = file.ReadToEnd( );
  232. 							}
  233. 							encoding = file.CurrentEncoding;
  234. 							file.Close( );
  235. 						}
  236. 					}
  237. 					else
  238. 					{
  239. 						return WriteError( String.Format( "File not found: \"{0}\"", filename ) );
  240. 					}
  241. 				}
  242.  
  243. 				#endregion Read input
  244.  
  245.  
  246. 				#region Replace text
  247.  
  248. 				if ( skipmatches == 0 && takematches == 0 )
  249. 				{
  250. 					result = Regex.Replace( input, pattern, replacement );
  251. 				}
  252. 				else
  253. 				{
  254. 					// Get all matches
  255. 					Regex regex = new Regex( pattern, regexoptions );
  256. 					MatchCollection matches = Regex.Matches( input, pattern, regexoptions );
  257. 					if ( matches.Count > skipmatches )
  258. 					{
  259. 						int counter = 0;
  260. 						int lastindex = 0;
  261. 						int replaced = 0;
  262. 						foreach ( Match match in matches )
  263. 						{
  264. 							if ( counter >= skipmatches && ( replaced < takematches || takematches == 0 ) )
  265. 							{
  266. 								result += input.Substring( lastindex, match.Index ) + match.ToString( );
  267. 								lastindex = match.Index + match.Length;
  268. 								replaced += 1;
  269. 							}
  270. 							counter += 1;
  271. 						}
  272. 						if ( lastindex < input.Length )
  273. 						{
  274. 							result += input.Substring( lastindex );
  275. 						}
  276. 					}
  277. 				}
  278.  
  279. 				#endregion Replace text
  280.  
  281.  
  282. 				#region Return results
  283.  
  284. 				if ( String.IsNullOrWhiteSpace( targetfile ) )
  285. 				{
  286. 					Console.Write( result );
  287. 				}
  288. 				else
  289. 				{
  290. 					using ( StreamWriter file = new StreamWriter( targetfile, false, Encoding.GetEncoding( encoding.CodePage ) ) )
  291. 					{
  292. 						file.Write( result );
  293. 						file.Close( );
  294. 					}
  295. 				}
  296.  
  297. 				return 0;
  298.  
  299. 				#endregion Return results
  300. 			}
  301. 			catch ( Exception e )
  302. 			{
  303. 				return WriteError( e.Message );
  304. 			}
  305.  
  306. 		}
  307.  
  308.  
  309. 		#region Redirection Detection
  310.  
  311. 		// Code to detect redirection by Hans Passant on StackOverflow.com
  312. 		// http://stackoverflow.com/questions/3453220/how-to-detect-if-console-in-stdin-has-been-redirected
  313. 		public static class ConsoleEx
  314. 		{
  315. 			public static bool OutputRedirected
  316. 			{
  317. 				get
  318. 				{
  319. 					return FileType.Char != GetFileType( GetStdHandle( StdHandle.Stdout ) );
  320. 				}
  321. 			}
  322.  
  323. 			public static bool InputRedirected
  324. 			{
  325. 				get
  326. 				{
  327. 					return FileType.Char != GetFileType( GetStdHandle( StdHandle.Stdin ) );
  328. 				}
  329. 			}
  330.  
  331. 			public static bool ErrorRedirected
  332. 			{
  333. 				get
  334. 				{
  335. 					return FileType.Char != GetFileType( GetStdHandle( StdHandle.Stderr ) );
  336. 				}
  337. 			}
  338.  
  339. 			// P/Invoke:
  340. 			private enum FileType { Unknown, Disk, Char, Pipe };
  341. 			private enum StdHandle { Stdin = -10, Stdout = -11, Stderr = -12 };
  342.  
  343. 			[DllImport( "kernel32.dll" )]
  344. 			private static extern FileType GetFileType( IntPtr hdl );
  345.  
  346. 			[DllImport( "kernel32.dll" )]
  347. 			private static extern IntPtr GetStdHandle( StdHandle std );
  348. 		}
  349.  
  350. 		#endregion Redirection Detection
  351.  
  352.  
  353. 		#region Error Handling
  354.  
  355. 		public static int WriteError( Exception e = null )
  356. 		{
  357. 			return WriteError( e == null ? null : e.Message );
  358. 		}
  359.  
  360. 		public static int WriteError( string errorMessage )
  361. 		{
  362. 			if ( string.IsNullOrEmpty( errorMessage ) == false )
  363. 			{
  364. 				Console.Error.WriteLine( );
  365. 				Console.ForegroundColor = ConsoleColor.Red;
  366. 				Console.Error.Write( "ERROR: " );
  367. 				Console.ForegroundColor = ConsoleColor.White;
  368. 				Console.Error.WriteLine( errorMessage );
  369. 				Console.ResetColor( );
  370. 			}
  371.  
  372. 			/*
  373. 			RxReplace,  Version 1.00
  374. 			Multi-line regex find and replace tool
  375.  
  376. 			Usage:   RXREPLACE  filename  pattern  replacement  [ options ]
  377. 			or:      some_command  |  RXREPLACE  pattern  replacement  [ options ]
  378.  
  379. 			Where:   filename       is the file to be filtered
  380. 			         some_command   is the command whose standard output is to be filtered
  381. 			         pattern        is the regex pattern for text to be replaced
  382. 			         replacement    replaces all matches (backreferences allowed, e.g. $1)
  383.  
  384. 			Options: /F:bytes       search the First specified number of bytes only
  385. 			         /I             makes the search case Insensitive
  386. 			         /O[:newfile]   Overwrite original, or write Output to newfile
  387. 			         /S:nn          Skip the first nn matches
  388. 			         /T:nn          Take only nn matches
  389.  
  390. 			Notes:   If /F:bytes is used and a file is specified, only the first bytes
  391. 			         of that file will be read; if the input is redirected, it is read
  392. 			         entirely, and will then be chopped to the specified number of bytes
  393. 			         before being searched. In both cases, the remainder of the input will
  394. 			         be discarded.
  395.  			         Unless /O switch is used, result will be written to Standard Output.
  396. 			         Backreferences in replacement can not be used with /S or /T.
  397.  
  398. 			Credits: Check for redirection by Hans Passant on StackOverflow.com:
  399. 			         http://stackoverflow.com/a/3453272
  400. 			         Array Slice extension by Sam Allen on DotNetPerls.com:
  401. 			         http://www.dotnetperls.com/array-slice
  402.  
  403. 			Written by Rob van der Woude
  404. 			http://www.robvanderwoude.com
  405. 			 */
  406.  
  407. 			Console.Error.WriteLine( );
  408.  
  409. 			Console.Error.WriteLine( "RxReplace,  Version {0}", progver );
  410.  
  411. 			Console.Error.WriteLine( "Multi-line regex find and replace tool" );
  412.  
  413. 			Console.Error.WriteLine( );
  414.  
  415. 			Console.Error.Write( "Usage:   " );
  416. 			Console.ForegroundColor = ConsoleColor.White;
  417. 			Console.Error.WriteLine( "RXREPLACE  filename  pattern  replacement  [ options ]" );
  418. 			Console.ResetColor( );
  419.  
  420. 			Console.Error.Write( "or:      " );
  421. 			Console.ForegroundColor = ConsoleColor.White;
  422. 			Console.Error.WriteLine( "some_command  |  RXREPLACE  pattern  replacement  [ options ]" );
  423. 			Console.ResetColor( );
  424.  
  425. 			Console.Error.WriteLine( );
  426.  
  427. 			Console.Error.Write( "Where:   " );
  428. 			Console.ForegroundColor = ConsoleColor.White;
  429. 			Console.Error.Write( "filename" );
  430. 			Console.ResetColor( );
  431. 			Console.Error.WriteLine( "       is the file to be filtered" );
  432.  
  433. 			Console.ForegroundColor = ConsoleColor.White;
  434. 			Console.Error.Write( "         some_command" );
  435. 			Console.ResetColor( );
  436. 			Console.Error.WriteLine( "   is the command whose standard output is to be filtered" );
  437.  
  438. 			Console.ForegroundColor = ConsoleColor.White;
  439. 			Console.Error.Write( "         pattern" );
  440. 			Console.ResetColor( );
  441. 			Console.Error.WriteLine( "        is the regex pattern for text to be replaced" );
  442.  
  443. 			Console.ForegroundColor = ConsoleColor.White;
  444. 			Console.Error.Write( "         replacement" );
  445. 			Console.ResetColor( );
  446. 			Console.Error.WriteLine( "    replaces all matches (backreferences allowed, e.g. $1)" );
  447.  
  448. 			Console.Error.WriteLine( );
  449.  
  450. 			Console.Error.Write( "Options: " );
  451. 			Console.ForegroundColor = ConsoleColor.White;
  452. 			Console.Error.Write( "/F:bytes" );
  453. 			Console.ResetColor( );
  454. 			Console.Error.Write( "       search the " );
  455. 			Console.ForegroundColor = ConsoleColor.White;
  456. 			Console.Error.Write( "F" );
  457. 			Console.ResetColor( );
  458. 			Console.Error.Write( "irst specified number of " );
  459. 			Console.ForegroundColor = ConsoleColor.White;
  460. 			Console.Error.Write( "bytes" );
  461. 			Console.ResetColor( );
  462. 			Console.Error.WriteLine( " only" );
  463.  
  464. 			Console.ForegroundColor = ConsoleColor.White;
  465. 			Console.Error.Write( "         /I" );
  466. 			Console.ResetColor( );
  467. 			Console.Error.Write( "             makes the search case " );
  468. 			Console.ForegroundColor = ConsoleColor.White;
  469. 			Console.Error.Write( "I" );
  470. 			Console.ResetColor( );
  471. 			Console.Error.WriteLine( "nsensitive" );
  472.  
  473. 			Console.ForegroundColor = ConsoleColor.White;
  474. 			Console.Error.Write( "         /O[:newfile]   O" );
  475. 			Console.ResetColor( );
  476. 			Console.Error.Write( "verwrite original, or write " );
  477. 			Console.ForegroundColor = ConsoleColor.White;
  478. 			Console.Error.Write( "O" );
  479. 			Console.ResetColor( );
  480. 			Console.Error.Write( "utput to " );
  481. 			Console.ForegroundColor = ConsoleColor.White;
  482. 			Console.Error.WriteLine( "newfile" );
  483. 			Console.ResetColor( );
  484.  
  485. 			Console.ForegroundColor = ConsoleColor.White;
  486. 			Console.Error.Write( "         /S:nn          S" );
  487. 			Console.ResetColor( );
  488. 			Console.Error.Write( "kip the first " );
  489. 			Console.ForegroundColor = ConsoleColor.White;
  490. 			Console.Error.Write( "nn" );
  491. 			Console.ResetColor( );
  492. 			Console.Error.WriteLine( " matches" );
  493.  
  494. 			Console.ForegroundColor = ConsoleColor.White;
  495. 			Console.Error.Write( "         /T:nn          T" );
  496. 			Console.ResetColor( );
  497. 			Console.Error.Write( "ake only " );
  498. 			Console.ForegroundColor = ConsoleColor.White;
  499. 			Console.Error.Write( "nn" );
  500. 			Console.ResetColor( );
  501. 			Console.Error.WriteLine( " matches" );
  502.  
  503. 			Console.Error.WriteLine( );
  504.  
  505. 			Console.Error.Write( "Notes:   If " );
  506. 			Console.ForegroundColor = ConsoleColor.White;
  507. 			Console.Error.Write( "/F:bytes" );
  508. 			Console.ResetColor( );
  509. 			Console.Error.Write( " is used and a file is specified, only the first " );
  510. 			Console.ForegroundColor = ConsoleColor.White;
  511. 			Console.Error.WriteLine( "bytes" );
  512. 			Console.ResetColor( );
  513.  
  514. 			Console.Error.WriteLine( "         of that file will be read; if the input is redirected, it is read" );
  515.  
  516. 			Console.Error.Write( "         entirely, and will then be chopped to the specified number of " );
  517. 			Console.ForegroundColor = ConsoleColor.White;
  518. 			Console.Error.WriteLine( "bytes" );
  519. 			Console.ResetColor( );
  520.  
  521. 			Console.Error.WriteLine( "         before being searched. In both cases, the remainder of the input will" );
  522.  
  523. 			Console.Error.WriteLine( "         be discarded." );
  524.  
  525. 			Console.Error.Write( "         Unless " );
  526. 			Console.ForegroundColor = ConsoleColor.White;
  527. 			Console.Error.Write( "/O" );
  528. 			Console.ResetColor( );
  529. 			Console.Error.WriteLine( " switch is used, result will be written to Standard Output." );
  530.  
  531. 			Console.Error.Write( "         Backreferences in " );
  532. 			Console.ForegroundColor = ConsoleColor.White;
  533. 			Console.Error.Write( "replacement" );
  534. 			Console.ResetColor( );
  535. 			Console.Error.Write( " can " );
  536. 			Console.ForegroundColor = ConsoleColor.White;
  537. 			Console.Error.Write( "not" );
  538. 			Console.ResetColor( );
  539. 			Console.Error.Write( " be used with " );
  540. 			Console.ForegroundColor = ConsoleColor.White;
  541. 			Console.Error.Write( "/S" );
  542. 			Console.ResetColor( );
  543. 			Console.Error.Write( " or " );
  544. 			Console.ForegroundColor = ConsoleColor.White;
  545. 			Console.Error.Write( "/T" );
  546. 			Console.ResetColor( );
  547. 			Console.Error.WriteLine( "." );
  548.  
  549. 			Console.Error.WriteLine( );
  550.  
  551. 			Console.Error.WriteLine( "Credits: Check for redirection by Hans Passant on StackOverflow.com:" );
  552.  
  553. 			Console.ForegroundColor = ConsoleColor.DarkGray;
  554. 			Console.Error.WriteLine( "         http://stackoverflow.com/a/3453272" );
  555. 			Console.ResetColor( );
  556.  
  557. 			Console.Error.WriteLine( "         Array Slice extension by Sam Allen on DotNetPerls.com:" );
  558.  
  559. 			Console.ForegroundColor = ConsoleColor.DarkGray;
  560. 			Console.Error.WriteLine( "         http://www.dotnetperls.com/array-slice" );
  561. 			Console.ResetColor( );
  562.  
  563. 			Console.Error.WriteLine( );
  564.  
  565. 			Console.Error.WriteLine( "Written by Rob van der Woude" );
  566.  
  567. 			Console.Error.WriteLine( "http://www.robvanderwoude.com" );
  568.  
  569. 			return 1;
  570. 		}
  571.  
  572. 		#endregion Error Handling
  573. 	}
  574.  
  575.  
  576. 	#region Extensions
  577.  
  578. 	// Array Slice
  579. 	// http://www.dotnetperls.com/array-slice
  580. 	public static class Extensions
  581. 	{
  582. 		/// <summary>
  583. 		/// Get the array slice between the two indexes.
  584. 		/// ... Inclusive for start index, exclusive for end index.
  585. 		/// </summary>
  586. 		public static T[] Slice<T>( this T[] source, int start, int end )
  587. 		{
  588. 			// Handles negative ends.
  589. 			if ( end < 0 )
  590. 			{
  591. 				end = source.Length + end;
  592. 			}
  593. 			int len = end - start;
  594.  
  595. 			// Return new array.
  596. 			T[] res = new T[len];
  597. 			for ( int i = 0; i < len; i++ )
  598. 			{
  599. 				res[i] = source[i + start];
  600. 			}
  601. 			return res;
  602. 		}
  603. 	}
  604.  
  605. 	#endregion Extensions
  606. }
  607.  

page last uploaded: 2017-07-06, 12:37