August 15, 2023

Simple Line Editor

Here is the LineEditor, it can work on a file or a string:

// Here is the definition of a line edit
public interface ILineEdit
{
    string ApplyEdit(string input);
}

/// <summary>
/// Perform a bunch of edits on a text file or string on a line by line basis. 
/// Only search and replace type edits are available.
/// </summary>
public class TextLineEditor
{
    private List<ILineEdit> _pendingEdits = new List<ILineEdit>();

    public TextLineEditor()
    {
    }

    public void ApplyEditsToFile(string inputPath, string outputPath)
    {
        Debug.Assert(!string.IsNullOrWhiteSpace(inputPath));
        Debug.Assert(!string.IsNullOrWhiteSpace(outputPath));
        Debug.Assert(!outputPath.Equals(inputPath)); 
        var inputFileInfo = new FileInfo(inputPath);
        var outputFileInfo = new FileInfo(outputPath);
        Debug.Assert(inputFileInfo.Exists);
        Debug.Assert(!outputFileInfo.Exists);

        using (TextWriter writer = new StreamWriter(outputFileInfo.FullName))
        {
            using (TextReader reader = new StreamReader(inputFileInfo.FullName))
            {
                ApplyEdits(writer, reader);
            }
        }
    }

    public string ApplyEditsToString(string text)
    {
        Trace.Assert(text != null);
        StringBuilder sb = new StringBuilder();
        using (TextWriter writer = new StringWriter(sb))
        {
            using (TextReader reader = new StringReader(text))
            {
                ApplyEdits(writer, reader);
            }
        }
        return sb.ToString();
    }

    private void ApplyEdits(TextWriter writer, TextReader reader)
    {
        string line = null;
        while ((line = reader.ReadLine()) != null)
        {
            foreach (ILineEdit edit in _pendingEdits)
            {
                line = edit.ApplyEdit(line);
            }
            if (line != null)
                writer.WriteLine(line);
        }
    }

    public void AddEdit(ILineEdit edit)
    {
        Trace.Assert(edit != null);
        _pendingEdits.Add(edit);
    }
}

This TextLineEditor could be replaced to work with IEnumerab<string> for line input and returning a IEnumerable<string> as an output. Here is a LineEdit example. Note that it can do deletes by replacing with a blank string.

public class SearchAndReplace : ILineEdit
{
    public enum RegExUsage
    {
        None = 0,
        Use
    }

    public SearchAndReplace()
    {
    }

    public SearchAndReplace(string searchFor, string replaceWith, bool extendedChars = false, RegExUsage regExUse = RegExUsage.None)
    {
        if (string.IsNullOrEmpty(searchFor))
            throw new ArgumentException("Cannot be null or an empty string", "searchFor");
        if (replaceWith == null)
            throw new ArgumentException("Cannot be null", "replaceWith");
        this.SearchFor = searchFor;
        this.ReplaceWith = replaceWith;
        this.ExtendedChars = extendedChars;
    }

    public bool IsRegEx { get; set; } = false;
    public string SearchFor { get; set; } = "";
    public string ReplaceWith { get; set; } = "";
    public bool ExtendedChars { get; set; } = false;

    public string ApplyEdit(string input)
    {
        if (ExtendedChars)
        {
            SearchFor = SearchFor.Replace("\\t","\t").Replace("\\n","\n").Replace("\\r","\r");
        }
        return (IsRegEx) ? SearchReplaceInString(input) : SearchReplaceInStringRegEx(input);
    }

    private string SearchReplaceInString(string input)
    {
        string output = input.Replace(SearchFor, ReplaceWith);
        return output;
    }

    private string SearchReplaceInStringRegEx(string input)
    {
        string output = Regex.Replace(input, SearchFor, ReplaceWith);
        return output;
    }
}

Here is an example usage:
{
	var inputResFilePath = ...
	var outputResNewCsvFilePath ...
	var outputResCsvFilePath = ...

	var inputCnbFilePath = ...
	var outputCnbNewCsvFilePath ...
	var outputCnbCsvFilePath = ...


	var replaceCommentChars = new SearchAndReplace("#", "*");
    {
        var replaceTabsWithGT = new SearchAndReplace("\t", " > ");

        TextLineEditor resEditor = new();
        resEditor.AddEdit(replaceTabsWithGT);
        resEditor.AddEdit(replaceCommentChars);
        
        resEditor.ApplyEditsToFile(inputResFilePath, outputResNewCsvFilePath);

        File.Delete(outputResCsvFilePath);
        File.Move(outputResNewCsvFilePath, outputResCsvFilePath);
    }
    {
        var replaceExclamationChars = new SearchAndReplace("!", "*");
        var replaceTabWithHyphen = new SearchAndReplace("\t", " - ");

        TextLineEditor cnbEditor = new();
        cnbEditor.AddEdit(replaceTabWithHyphen);
        cnbEditor.AddEdit(replaceCommentChars);
        cnbEditor.AddEdit(replaceExclamationChars);

        cnbEditor.ApplyEditsToFile(inputCnbFilePath, outputCnbNewCsvFilePath);

        File.Delete(outputCnbCsvFilePath);
        File.Move(outputCnbNewCsvFilePath, outputCnbCsvFilePath);
    }
}