Introduction
A small application addressing one task: remove lines containing specific words from a bunch of files under given folder.
Background
Once a while I get a task need to clear something from existing text files which are well formatted.
At first I search the keyword using NodePad++ and then double click and delete
the lines one by one manually, but soon I find that there are too many lines to remove and doing it manually is boring and cannot guarantee correctness.
So I begin the code of this small tool written in WPF.
Using the code
To use the tool, follow steps below:
- Set the string/words to search.
- Set the folder to detect files.
- Set the file pattern which can contain wildcards.
E.g., *.txt for all Text files; prefix*.txt for all test files naming start with 'prefix'.
- Click 'Analyze' button to preview the lines that will be removed.
- And then click 'Remove lines' and it will do the job to clean up the files.
Here is code for
SearchAndRemoveViewModel
:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.ViewModel;
namespace LineRemover.ViewModel
{
class SearchAndRemoveViewModel : NotificationObject, IDataErrorInfo
{
private string _searchString;
private string _searchFolder;
private string _filePattern;
private string _status;
private readonly List<string> _filesToModify = new List<string>();
public string SearchFolder
{
get { return _searchFolder; }
set
{
if (value == _searchFolder) return;
_searchFolder = value;
RaisePropertyChanged("SearchFolder");
RaiseCanExecuteChanged();
}
}
public string SearchString
{
get { return _searchString; }
set
{
if (_searchString != value)
{
_searchString = value;
RaisePropertyChanged("SearchString");
RaiseCanExecuteChanged();
}
}
}
public string FilePattern
{
get { return _filePattern; }
set
{
if (_filePattern != value)
{
_filePattern = value;
RaisePropertyChanged("FilePattern");
}
}
}
public string Status
{
get { return _status; }
set
{
if (_status == value) return;
_status = value;
RaisePropertyChanged("Status");
}
}
private readonly ObservableCollection<MatchResult> _searchResults =
new ObservableCollection<MatchResult>();
public ObservableCollection<MatchResult> AllResults { get { return _searchResults; } }
public SearchAndRemoveViewModel()
{
SearchString = "Results";
SearchFolder = @"t:\components";
}
private void RaiseCanExecuteChanged()
{
AnalyzeCommand.RaiseCanExecuteChanged();
ApplyCommand.RaiseCanExecuteChanged();
}
private DelegateCommand _analyzeCommand;
public DelegateCommand AnalyzeCommand
{
get
{
return _analyzeCommand ?? (_analyzeCommand =
new DelegateCommand(Analyze, CanExecuteAnalyze));
}
}
private DelegateCommand _applyCommand;
public DelegateCommand ApplyCommand
{
get { return _applyCommand ?? (_applyCommand = new DelegateCommand(Apply, CanAppy)); }
}
private bool CanExecuteAnalyze()
{
return this["SearchString"] == null &&
this["SearchFolder"] == null;
}
private bool CanAppy()
{
return this._filesToModify.Any();
}
private void Analyze()
{
_filesToModify.Clear();
try
{
var allResults = new List<MatchResult>();
var files = Directory.GetFiles(SearchFolder, FilePattern ?? "*");
foreach (var file in files)
{
var results = AnalyzeOneFile(file);
if (results.Any())
{
_filesToModify.Add(file);
allResults.AddRange(results);
}
}
AllResults.Clear();
allResults.ForEach(r=>AllResults.Add(r));
}
catch (Exception e)
{
MessageBox.Show(e.ToString(), "Error");
}
RaisePropertyChanged("AllResults");
ApplyCommand.RaiseCanExecuteChanged();
}
private void Apply()
{
try
{
foreach (var file in _filesToModify)
{
Apply(file);
}
Status = string.Format("updated {0} files.", _filesToModify.Count);
_filesToModify.Clear();
AllResults.Clear();
}
catch (Exception e)
{
MessageBox.Show(e.ToString(), "Error");
}
}
private void Apply(string file)
{
Status = "applying to " + file;
var lines = File.ReadAllLines(file);
bool noChange = true;
var updated = new List<string>();
foreach (var line in lines)
{
if (line != null && line.IndexOf(SearchString,
StringComparison.InvariantCultureIgnoreCase) < 0)
{
updated.Add(line);
}
else
{
noChange = false;
}
}
if (!noChange)
{
using (var stream = File.OpenWrite(file))
{
using (var writer = new StreamWriter(stream))
{
writer.BaseStream.Seek(0, SeekOrigin.Begin);
writer.BaseStream.SetLength(writer.Encoding.GetPreamble().Length);
foreach (var line in updated)
{
writer.WriteLine(line);
}
writer.Flush();
}
}
}
Status = "applied to " + file;
}
private IList<MatchResult> AnalyzeOneFile(string file)
{
var results = new List<MatchResult>();
var lines = File.ReadAllLines(file);
int lineNo = 0;
foreach (string line in lines)
{
if (line != null && line.IndexOf(SearchString,
StringComparison.InvariantCultureIgnoreCase) >= 0)
{
var result = new MatchResult { LineNo = lineNo, LineContent = line, FileName = file };
results.Add(result);
}
++lineNo;
}
return results;
}
public string this[string columnName]
{
get
{
if (columnName == "SearchString")
{
if (String.IsNullOrEmpty(SearchString))
{
return "search string is required.";
}
}
if (columnName == "SearchFolder")
{
if (String.IsNullOrEmpty(SearchFolder))
{
return "search folder is required.";
}
}
return null;
}
}
public string Error { get; private set; }
}
}
Points of Interest
As an engineer surely we can figure out ways to automate manual work.