Feedback

C# - führende Leerzeichen mehrzeiliger Strings gleichm. entfernen V2

Veröffentlicht von am 11/17/2015
(0 Bewertungen)
zwei statische öffentliche Methoden (1x für die Übergabe eines Textfiles, 1x zur direkten Übergabe eines mehrzeiligen Strings mit Zeilenumbrüchen)

Der übergebene String wird anhand von Zeilenumbrüche aufgeteilt und auf das erste Vorkommen eines Whitespace-Zeichens in einer nicht leeren Zeile geprüft. Anschließend wird über regex aus jeder Zeile die gleiche Anzahl an führenden, gleichartigen Whitespace-Zeichen entfernt, wobei die Zeile mit den wenigsten vorangestellten Whitespace-Zeichen als Referenz gilt.

Auf Exceptionhandling, Performanceoptimierung und ähnliches wurde im Snippet bewusst verzichtet. Dies kann gerne über die Kommentare geschehen.

Anhand der Kommentare der Version 1 sind folgende Verbesserungen eingeflossen:

1. Es werden nun zuerst Kontrollzeichen für den Zeilenumbruch gesucht
2. Es wird anhand des ersten Zeichens der ersten nicht leeren Zeile entschieden, welches Whitespacezeichen verwendet wurde und dessen Häufigkeit bis zum ersten anderen Zeichen geprüft.
3. Es wird geprüft, ob dieses gefundene Zeichen ein Whitespacezeichen ist
4. Es wird nun (wie im Kommentar des Originalsnippets als Alternative beschrieben), der komplette mehrzeilige String über regex bearbeitet und nicht mehr zeilenweise in einer Schleife
5. Die Zeile mit den wenigsten vorangestellten Whitespace-Zeichen gilt als Referenz

Verbesserungspotential:
Wenn abwechselnd Leerzeichen und Tabs verwendet werden, wird nur das erste Whitespacezeichen gewertet. Somit können Whitespaces for dem eigentlichen Textbeginn stehen bleiben.
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;


namespace WhitespaceRemover
{
    class Program
    {
        static void Main(string[] args)
        {
            WhitespaceRemover.ProcessFile(args[0]);
        }
    }


    public static class WhitespaceRemover
    {        
        /// <summary>
        /// reads a textfile and writes the processed content back to a new textfile
        /// </summary>
        /// <param name="file"></param>
        public static void ProcessFile(string file)
        {
            //probably not the most beautiful way for getting the filename but it works :)
            var newfile = Regex.Replace(file, @"^(.*?)(\..+)?$","$1_removed$2");

            //call the processing method and write the processed content to the new file.
            using (var swr = new StreamWriter(newfile))
            {
                swr.Write(ProcessString(File.ReadAllText(file)));
            }           
        }

        /// <summary>
        /// removes an equal number of whitespaces on each line of a multiline string, taking the first line as reference
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static string ProcessString(string text)
        {
            
            //find the new line control characters
            var newlinecontrolcharacters = Regex.Replace(text, @"^.*?(\r?\n)", "$1|", RegexOptions.Multiline).Split('|')[0];
            
            //splits the multiline string into an array using the new line control characters
            var textLines = text.Split(new string[] { newlinecontrolcharacters }, StringSplitOptions.None);
            
            char[] characters = null;
            char whitespacecharacter = default(char);
            var count = 0;
            var minappearances = 0;
            bool minappearancesassigned = false;
            bool characterfound = false;

            //split the first line into a character array   
            for (int i = 0; i < textLines.Length; i++)
            {
                if(!string.IsNullOrWhiteSpace(textLines[i]))
                {
                    characters = textLines[i].ToCharArray();
                    if (!characterfound)
                    {
                        //first character is defined as whitespace character that will be removed
                        whitespacecharacter = characters[0];
                        
                        //if first character is not a whitespace character, keep on searching
                        if (!Regex.IsMatch(whitespacecharacter.ToString(), @"\s"))
                        {
                            continue;
                        }
                        else
                        {
                            characterfound = true;
                        }
                    }

                    //finally loop through it as long as there are whitespace characters and count them
                    while (characters[count] == whitespacecharacter) count++;

                    //assign the value of count to minappearances, if a line was found with less whitespace characters
                    if(minappearances > count || (minappearances == 0 && !minappearancesassigned))
                    {
                        minappearances = count;
                        minappearancesassigned = true;
                    }

                    count = 0;
                }

            }
            
            if(minappearances == 0 || characterfound == false) 
                return text;

            //do the replacements for the whole file
            return Regex.Replace(text, string.Format("^{0}{1}(.*?)$", whitespacecharacter.ToString(), string.Concat(new string[]{"{",minappearances.ToString(),"}"})), "$1", RegexOptions.Multiline);
        }
    }
}

Abgelegt unter regex, whitespace.

8 Kommentare zum Snippet

SteeW schrieb am 11/17/2015:
Dinge, die mir noch nicht gefallen, die ich aber in diesem Snippet (und nicht in einem neuen) beheben werde:
1. Suche nach der Zeile mit den wenigsten vorangestellten Whitespace Zeichen
2. .. to be continued :)
Anonymous2 schrieb am 11/18/2015:
1. Pfade müssen kein \ enthalten.
2. Für die Eingabe
   a
b
c

sieht das Ergebnis nicht so aus, wie man es eigentlich erwarten würde.
3. Dass Tabs und Leerzeichen gemischt werden, kommt zumindest in Quellcode leider recht oft vor.
SteeW schrieb am 11/18/2015:
Danke für die Kommentare.
zu 1.: Das Snippet behandelt das Entfernen von Leerzeichen. Die Methode, die eine Textdatei entgegen nimmt ist meiner Meinung nach so einfach, dass sie sich jeder für seine Umgebung anpassen kann. Ich kann es aber gerne beim nächsten Update berücksichtigen. Ich würde einfach den Originalpfad nehmen und an den Dateinamen "_removed" anhängen.
zu 2.: Wie hätte "man es eigentlich" erwartet? Es wäre hilfreich zu schreiben was "man eigentlich" erwartet, da ich keine Gedanken lesen kann. Ist die erste Zeile weiter eingerückt als die zweite, kommt dabei nichts vernünftiges raus, weil der Regex nicht so viele Whitespacezeichen entfernen kann, wie ihm mitgeteilt wird. Ohne getestet zu haben würde ich sagen, die zweite Zeile bleibt wie sie ist. In diesem Fall geben ich Ihnen vollkommen recht. Dies ist noch ein offener Punkt, den ich vielleicht heute Abend anpassen werde: Prüfen, welches die am wenigsten eingerückte Zeile ist und diese als Referenz nehmen.
zu 3.: Ist mir bisher zum Glück noch nicht untergekommen. Und wenn doch, ist es das erste was mit Bordmitteln des eingesetzten Editors mache: Alle Tabs in Leerzeichen umwandeln in festgelegter Anzahl (in meinem Fall 4). Kann ich mir aber noch ansehen und eine Lösung dafür finden. Ggf. unter Berücksichtung des Kommentars von koopakilla zur Version 1 des Snippets
Anonymous2 schrieb am 11/18/2015:
zu 2: Erwarten würde ich es, wie es zB Notepad++ macht, die Zeilen sind dann trotzdem getrimmt, obwohl es nur 2 statt 3 Leerzeichen sind.
zu 3: Die Funktion gar nicht benutzen.^^ Ich hatte zumindest noch nie den Wunsch die Funktion automatisch erledigen zu lassen, sondern das vom Editor machen lassen. ;)
SteeW schrieb am 11/18/2015:
Punkt 1: Der neue Dateiname heißt nun exakt so, wie der übergebene mit angehängtem _removed vor der Dateiendung, wenn es eine gibt. Wie bereits einleitend erwähnt, gibt es kein Exceptionhandling und es müsste geprüft werden, ob man Lese-/Schreibrechte auf den Pfad und die Datei besitzt.

Punkt 2: Es wird jetzt die Zeile gesucht, die die wenigsten vorangestellten Whitespace-Zeichen besitzt und als Referenz genommen.

Punkt 3: nicht in das Snippet übernommen. Ich denke auch, dass man das in der Regel einem geeigneten Editor überlassen sollte. In einem eigenen kleinen Programm könnte man das Snippet mehrmals hintereinander mit anschließender manueller Prüfung laufen lassen, bis man mit dem Ergebnis zufrieden ist. Wenn man jedoch komplett Kraut und Rüben bekommt, was Whitespace-Zeichen angeht, dann sollte man meiner Meinung nach tatsächlich auf einen richtigen Editor zurückgreifen (oder eine andere Quelle *g*)
Anonymous2 schrieb am 11/19/2015:
Du scheinst ein Freund von Regex zu sein... Dein Pfad-Code funktioniert aber nicht richtig, da ein Pfad immernoch nicht zwingend \ enthalten muss. C:/Windows/... ist auch ein gültiger Pfad, den du unter Windows verwenden kannst. Das was du da per Regex machst, würde man mit Bordmitteln so machen:
var newfile = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path) + "_removed" + Path.GetExtension(path));
SteeW schrieb am 11/19/2015:
Ja, ich bin ein Freund von Regex und schreibfaul. Sie scheinen kein Freund von Regex zu sein oder das Pattern zumindest falsch gelesen zu haben. Der \ im Pattern ist ein Escape Zeichen für den nachfolgenden Punkt und hat nichts mit einem Pfad zu tun. Ihre Kritik, es würde nicht funktionieren ist somit falsch.
Sie haben jedoch recht,dass man es auch anders machen kann. Vielen Dank für das Beispiel.
Zugegeben, die Methode gibt es eigentlich nur, damit ich schöner Debuggen kann ;)
Anonymous2 schrieb am 11/19/2015:
Stimmt, das muss ich zurücknehmen mit dem \. Und doch, ich bin ein Freund von Regex, aber nur, wenn es sich wirklich lohnt. Einen Regex auszuwerten ist keine "billige" Funktion, da geht vieles mit Bordmitteln schneller, wenn man weiß, dass es sie gibt.
 

Logge dich ein, um hier zu kommentieren!