Leerzeilen löschen mit reg-ex nach Bedingung

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Leerzeilen löschen mit reg-ex nach Bedingung



    Ich möchte alle mit Text gefüllten Dateien in einem Ordner für die weitere Verwendung formatieren.

    Die Dateien sollen als erste Zeile mit dem Liedtitel beginnen, die mit einer Liednummer beginnt.

    Also sollen alle Leerzeilen vor dem Titel, der durch die Nummer erkennbar ist, gelöscht werden.

    Der Titel soll durch 2 Leerzeilen von der darauf folgenden 1. Strophe getrennt sein.
    Mehr Leerzeilen sollen gelöscht, zuwenige dazu gefügt werden.

    Die Strophen selbst soll abwechselnd, beginnend mit Text, Textzeile und Leerzeile sein.

    Die einzelnen Strophen sind durch Leerzeilen getrennt und daran erkennbar, das der Rhythmus 1 Text 1 Leer unterbrochen wird und danach noch Text kommt. Oder wie im Fall der 2. Strophe wenn nach einem Block von Textzeilen noch Text kommt. Der Abstand der einzelnen Strophen soll also 2 Leerzeilen sein.

    Alle Leerzeilen nach der letzten Textzeile sollen gelöscht werden.

    Beispieltext:



    001. Liedtitel (Text)



    Text

    Text

    Text

    Text



    Text
    Text
    Text
    Text


    Text

    Text

    Text



    #z.B letzte Zeile
    Sollzustand

    001. Liedtitel (Text)


    Text

    Text

    Text

    Text


    Text

    Text

    Text

    Text


    Text

    Text

    Text
    # letzte Zeile
    Weil ich nach dieser Aufgabe noch weitere Einsetzungen habe, dachte ich, das der Einsatz der regulären Ausdrücke die richtige Wahl ist.

    Die Frage ist, ob solch eine Anforderung mit einem regulären Ausdruck zu erfassen ist und ob gerade jemand den passenden Ausdruck kennt oder ob nicht vielleicht die Aufgabe besser über das Dateisystem oder über Arrays zu erfüllen währe.

    Danke soweit und Gruß

    Estrela
    Denk positiv.

  • #2
    RegEx kann schon viel, aber für deise Aufgabe nicht gerade sinnvoll. Ich würde sagen, dass du entweder mit fopen, fread Zeile für Zeile einliest und weg schreibst oder mit files komplet einlesen, das Array durchsteppen und weg schreiben. Es ist nur ein paar Zeilen Code, es ist schnell erstellt anstatt sich den Kopf über RegEx und seine Tücken zu zerbrechen.

    Kommentar


    • #3
      Hallo asp2php.

      Dachte ich mir schon, das diese Aufgabe als Aufwärmübung für RegEx nicht unbedingt geeignet ist.

      Und das mit dem durchsteppen ist für mich leider nicht so einfach.

      Mit diesem Code ist der Zugriff auf die einzelne Zeile möglich:

      PHP-Code:
      <?php
      $d 
      "test.txt";
      $file file$d);
      foreach( 
      $file as $key => $line) {
          echo 
      $line;
      }
      ?>
      Aber wie und wo baue ich denn meine Abfragen ein. In der Schleife selbst ist doch nur die einzelne Zeile bekannt und nicht das Verhältnis zur Nachbarschaft.
      Also brauche ich die Schleife dort nicht.
      Kannst Du mir nicht mal ein Beispiel nennen, wie ich das mit dem Abstand der Titelzeile zur ersten Strophe überprüfe?

      Gruß

      Estrela
      Zuletzt geändert von Estrela; 27.05.2009, 17:59.
      Denk positiv.

      Kommentar


      • #4
        Beim Durchsteppen schmeißt du einfach alle Leerzeilen raus, d.h. überspringen, und schreibst in die neue Datei nur das, was du willst. Wo ist denn dein Problem?

        Kommentar


        • #5
          Zitat von Estrela Beitrag anzeigen
          Aber wie und wo baue ich denn meine Abfragen ein. In der Schleife selbst ist doch nur die einzelne Zeile bekannt und nicht das Verhältnis zur Nachbarschaft.
          Die vorherigen Zeilen sind alle schon "bekannt" - schliesslich hast du sie schon durchlaufen. Ist halt nur die Frage, wie viele davon du dir "merken" musst, um die Daten zu haben, die du für deinen Vergleich brauchst.


          (Und wenn man mit einem nummerisch indizierten Array und einer for-Schleife arbeitet, dann sind auch die folgenden Zeilen schon "bekannt", bzw. abfragbar. Aber sowas braucht man im Normalfall nicht, da reicht es, wenn man "nach hinten" schauen kann, auch noch nach vorne ist selten notwendig.)
          I don't believe in rebirth. Actually, I never did in my whole lives.

          Kommentar


          • #6
            @asp2php
            Ja genau, das ist ein guter Gedanke.

            Einfach beim Einlesen alle Leerzeilen raus und danach das Dokument so aufbauen, wie es sein soll.

            Wenns Probleme gibt, melde ich mich nochmal.

            Danke also

            und Gruß

            Estrela
            Denk positiv.

            Kommentar


            • #7
              @wahsaga

              Wenn das Programm also in einem nummerisch indizierten Array und einer for-Schleife ist( was ja hier der Fall ist), dann kann ich abfragen, wenn das und das in der nächsten und/oder übernachsten usw Zeile ist, dann tue das und das?

              Wie formuliere ich das denn in php?

              Zeig mit doch bitte mal, wie eine solche exemplarische Abfrage aussieht.

              Gruß

              Estrela
              Zuletzt geändert von Estrela; 27.05.2009, 18:30.
              Denk positiv.

              Kommentar


              • #8
                @asp2php

                Beim Durchdenken entstand schon ein Problem.

                Die Strophen gehen verlohren.

                Also, da die Anzahl der Zeilen in einer Strophe variieren kann, muß zuerst die Struktur der Strophen ermittelt werden, hier also, wieviele Zeilen eine Strophe hat.

                Wie mache ich das am besten?

                Gruß

                Estrela
                Denk positiv.

                Kommentar


                • #9
                  Zitat von Estrela Beitrag anzeigen
                  @wahsaga

                  Wenn das Programm also in einem nummerisch indizierten Array und einer for-Schleife ist( was ja hier der Fall ist), dann kann ich abfragen, wenn das und das in der nächsten und/oder übernachsten usw Zeile ist, dann tue das und das?

                  Wie formuliere ich das denn in php?

                  Zeig mit doch bitte mal, wie eine solche exemplarische Abfrage aussieht.

                  Gruß

                  Estrela
                  Seufz, due liest doch via file() ein, wer hindert dich daran mit:

                  PHP-Code:
                  $content file(...);
                  $cnt count($content)
                  for (
                  $i=0$i<$cnt$i++)
                  ... 
                  durchzulaufen.

                  Zitat von Estrela Beitrag anzeigen
                  @asp2php

                  Beim Durchdenken entstand schon ein Problem.

                  Die Strophen gehen verlohren.

                  Also, da die Anzahl der Zeilen in einer Strophe variieren kann, muß zuerst die Struktur der Strophen ermittelt werden, hier also, wieviele Zeilen eine Strophe hat.

                  Wie mache ich das am besten?

                  Gruß

                  Estrela
                  Worin unterscheiden sich die Strophen und Refrain? Untersuche das und finde das Muster heraus, dann kannst du einbauen.

                  Mensch, solche Basic musst du eigentlich so aus dem Ärmel schütteln können.

                  Kommentar


                  • #10
                    Zitat von Estrela Beitrag anzeigen
                    Wie formuliere ich das denn in php?

                    Zeig mit doch bitte mal, wie eine solche exemplarische Abfrage aussieht.
                    Überlegen wir mal ...

                    Wenn die Zählervariable einer for-Schleife, nennen wir sie mal $i, für den Zugriff auf die aktuelle Zeile dient - wie könnte man dann wohl auf die vorherige oder nachfolgende Zeile zugreifen?
                    Vielleicht, in dem wir uns (vage) erinnern, dass es sowas wie Plus und Minus gibt, und ausserdem noch eine komische Zahl, die "Eins" heisst ...?

                    (Natürlich muss man dabei darauf achten, dass man nicht auf nicht existente Zeilen zugreift - also bspw. die vor der ersten oder die nach der letzten, die gibt's nämlich nicht.)
                    I don't believe in rebirth. Actually, I never did in my whole lives.

                    Kommentar


                    • #11
                      @asp2php
                      Mensch, solche Basic musst du eigentlich so aus dem Ärmel schütteln können.
                      Ja danke, bin auf dem Wege, die Diskrepanz zwischen Denken und können abzubauen.

                      Istzustand:
                      PHP-Code:
                      <?php
                      $d 
                      "test.txt";
                      $content file$d);
                      $cnt count$content);
                      for( 
                      $i=0$i<$cnt$i++) {
                          if( 
                      ereg("^[0-9]"$content$i])) {
                              echo 
                      "<br>Titel : $content[ $i]";
                              
                      $a_titel[] = $i;
                              
                      $i++;
                          } 
                          if( 
                      ereg"^[a-zA-Z]"$content$i])) {
                              echo 
                      "<br>Strophenzeile : $content[ $i]";
                              
                      $a_strop[] = $i;
                          }else {
                              echo 
                      "<br>Leerzeile : $content[ $i]";
                              
                      $a_leer[] = $i;
                          }
                      }
                      print_r $a_titel);
                      print_r $a_strop);
                      print_r $a_leer);
                      ?>
                      So, jetzt habe ich also die Zeilennummern im Array, das ich jetzt auf die Muster untersuchen kann.

                      So weit so gut.

                      Oder kann ich schon direkt nach einer Regel in der Schleife die überzähligen Leerzeilen löschen?

                      Estrela
                      Zuletzt geändert von Estrela; 27.05.2009, 22:56.
                      Denk positiv.

                      Kommentar


                      • #12
                        Zitat von Estrela Beitrag anzeigen
                        ...
                        Und das mit dem durchsteppen ist für mich leider nicht so einfach.
                        Wieso nicht? Du kuckst dir einfach ("step-by-step") jede Zeile an, überprüfst ob sie "leer" ist und überspringst Leerzeilen, die zu viel sind und fügst notwendige Leerzeilen hinzu.
                        Wie du schon selbst richtig erkannt hast:

                        Oder kann ich schon direkt nach einer Regel in der Schleife die überzähligen Leerzeilen löschen?
                        Siehe auch etwas weiter unten im Text ...

                        ... Istzustand:
                        PHP-Code:
                        <?php
                        $d 
                        "test.txt";
                        $content file$d);
                        $cnt count$content);
                        for( 
                        $i=0$i<$cnt$i++) {
                            if( 
                        ereg("^[0-9]"$content$i])) {
                                echo 
                        "<br>Titel : $content[ $i]";
                                
                        $a_titel[] = $i;
                                
                        $i++;
                            } 
                            if( 
                        ereg"^[a-zA-Z]"$content$i])) {
                                echo 
                        "<br>Strophenzeile : $content[ $i]";
                                
                        $a_strop[] = $i;
                            }else {
                                echo 
                        "<br>Leerzeile : $content[ $i]";
                                
                        $a_leer[] = $i;
                            }
                        }
                        print_r $a_titel);
                        print_r $a_strop);
                        print_r $a_leer);
                        ?>
                        So, jetzt habe ich also die Zeilennummern im Array, das ich jetzt auf die Muster untersuchen kann.

                        So weit so gut.
                        Eher: So weit, so gut gemeint ...
                        Was nützt dir die Gesamtzahl der jeweiligen Zeilen-Arten? Richtig: Nix, überhaupt nix.
                        Und mit deinen regulären Ausdrücken dürfte es Probleme geben, wenn beispielsweise eine Textzeile mit Ä, Ö oder Ü anfangen sollte. Ich hoffe mal, du hast nur englische Liedtexte -- da könnte deine Mustersuche klappen. ;-)

                        Dachte ich mir schon, das diese Aufgabe als Aufwärmübung für RegEx nicht unbedingt geeignet ist.
                        Warum sollte sie das nicht sein? Die hohe RegEx-Kunst ist es bestimmt nicht, also ist Aufwärmübung gar keine so schlechte Bezeichnung für dein Problem. ;-)

                        "Reguläre Ausdrücke" anzuwenden, bedeutet "Erkennen von Mustern".
                        Du hast aber nicht gründlich genug nach Mustern (oder Regeln) gesucht -- obwohl deine Problembeschreibung welche enthält. Wie so oft, ist eine gute Problembeschreibung die halbe Lösung.

                        Regeln:
                        1) 0 Leerzeilen (also ein Zeilenumbruch) soll zu einer Leerzeile werden
                        2) eine Leerzeile soll zu einer Leerzeile werden
                        3) zwei und mehr Leerzeilen sollen zu zwei Leerzeilen werden

                        Ausnahmen:
                        4) Leerzeilen vor der ersten und nach der letzten Text-Zeile sollen komplett entfernt werden.

                        Wenn wir die Ausnahmen erstmal ignorieren, können wir uns an Hand der Regeln
                        einen RegEx basteln. Dabei gilt:
                        * Null Leerzeilen entsprechen einem Zeilenumbruch.
                        * Eine Leerzeile entspricht zwei Zeilenumbrüchen.
                        * Zwei Leerzeilen entspricht drei Zeilenumbrüchen.
                        * usw.

                        Dass bauen wir als Perl-kompatiblen RegEx:

                        Zeilenumbruch (idiotensicher):
                        /(\r\n|[\n\r])/ passt auf alles, was wie gängige Zeilenumbrüche aussieht.

                        eine Leerzeile:

                        /(\r\n|[\n\r])\s*?\\1/
                        * Zeilenumbruch,
                        * keins oder beliebig viele Whitespace-Zeichen,
                        * gefolgt vom gleichen Zeilenumbruch wie der erste

                        Auf mehrere Leerzeilen passt der Ausdruck, wenn man den letzten Teil des Musters wiederholt.

                        Das Fragezeichen sorgt dafür, dass keine Zeilenumbruchzeichen mit \s* überlesen werden, denn auch Zeilenumbrüche werden zu den Whitespace-Zeichen gezählt.

                        Whitespace-Zeichen sind Leerzeichen, Tabulatoren, Zeilenumbrüche und diverser anderer (meist) unsichtbarer Kram. Da man sowas schonmal aus Versehen eintippen und beim bloßen Anschauen im Texteditor nicht immer erkennen kann, hab ich das mit eingebaut.

                        Jetzt kommt das Problem des Ersetzens. Großes Rumfummeln mit preg_match() und den diversen Zeichenketten-Manipulationsfunktionen liegt mir nicht, also nehmen wir preg_replace_callback(). Dss gewöhnliche preg_replace() reicht hier nicht, weil wir abhängig von der Zeilenzahl unterschiedliche Ersetzungs-Muster brauchen.

                        Damit die Callback-Funktion weiß, welchen Fall (0, 1, 2 oder mehr Zeilen) sie vor sich hat, müssen wir ihr das mitteilen. Und, schwupps, wird der RegEx etwas komplizierter:

                        '/\s*?(\r\n|[\n\r])(\s*?\\1(\s*?\\1)*)?/'

                        * Das erste \s*? sammelt nur vor dem ersten Zeilenumbruch rumliegende
                        Whitespaces ein, tut also nichts zur Sache

                        * Dann suchen wir nach dem ersten Zeilenumbruch und merken uns den
                        als nummeriertes "Teilmuster" (== "sub-pattern") 1.

                        * Der nächste Ausdruck (\s*?\\1(\s*?\\1)*)? kann einmal oder keinmal vorkommen.
                        Im letzteren Fall hätten wir eine Null-Leerzeile gefunden.
                        Im ersteren Fall haben wir Teilmuster 2.

                        * Kommt der Ausdruck einmal vor, muss weiter aufgedröselt werden. Wir sehen zwei
                        Teilmuster:
                        \s*?\\1 und (\s*?\\1)*
                        Das erste passt auf eine Leerzeile, das zweite auf alle weiteren Leerzeilen
                        und wird als Teilmuster 3 nummeriert.

                        Die Klammern erzeugen im Treffer-Array ($hits) jeweils einen neuen Eintrag mit der
                        jeweiligen Teilmuster-Nummer, wenn das Suchmuster passt. Diese Einträge lassen
                        sich mit isset() einfach abfragen, ohne großartige Berechnungen oder
                        Zeichenzählereien anzustellen.

                        Die Tricksereien, um die Leerzeilen am Anfang und Ende der Datei zu entfernen,
                        überlasse ich dem geneigten Leser zur Übung. ;-)
                        Die Erkennung des Anfangs geht dabei ganz ohne reguläre Ausdrücke über
                        die Bühne.

                        PHP-Code:
                        /// formatiert zeilen in $src neu
                        /// gibt einen String zurück
                        function reformat(
                          
                        $src
                        ) {
                          
                        // (\s*?\\1(\s*?\\1)*(\z)?)? fügt bei nur einem Zeilenumbruch am Ende eine Leerzeile ein
                          // (\s*?\\1(\s*?\\1)*(\z)?|(\z))? behebt das Problem (und auch nur in dem Fall)

                          // der RegEx
                          
                        static $pcre '/\s*?(\r\n|[\n\r])(\s*?\\1(\s*?\\1)*(\z)?|(\z))?/S';
                          return 
                        preg_replace_callback($pcre'reformat_callback'$src);
                        }

                        /// Callback-Funktion, bekommt Treffer-Array
                        /// gibt Ersetzen-String zurück
                        function reformat_callback($hits) {
                          static 
                        $is_start TRUE// ist nur beim ersten Durchlauf TRUE
                          
                          // Zeilen am Anfang entfernen (== durch Leerstring ersetzen)
                          
                        if ($is_start) {
                            
                        $is_start FALSE;
                            return 
                        '';
                          }
                          
                          
                        // Zeilen am Ende entfernen
                          
                        if (isset($hits[4])) {
                            
                        $is_start TRUE// fürs nächste Mal wieder auf Start setzen
                            
                        return '';
                          }

                          
                        // als Zeilenumbruch wird "\r\n" angenommen

                          // >= 2 Leerzeilen zu 2 Leerzeilen
                          
                        if (isset($hits[3])) {
                            return 
                        "\r\n\r\n\r\n"// 3 Umbrüche == 2 Leerzeilen
                          
                        }
                          
                          
                        // 1 Leerzeile zu 1 Leerzeil
                          // 0-Leerzeile zu 1 Leerzeile
                          
                        return "\r\n\r\n"// 2 Umbrüche == 1 Leerzeile

                        Das Ganze auf dein Beispiel angewandt:
                        PHP-Code:
                        $src '



                        001. Liedtitel (Text)


                        Text

                        Text
                        Text

                        Text



                        Text
                        Text
                        Text
                        Text


                        Text

                        Text

                        Text



                        #z.B letzte Zeile
                        '
                        ;

                        /// Hilfsfunktion: zeigt eine Zeichenkette formatiert als HTML an
                        function display(
                          
                        $txt
                        ) {
                          echo 
                        '<hr /><pre>'htmlspecialchars($txt), '</pre><hr />';
                        }

                        // vorher anzeigen
                        display($src);
                        // umformatieren
                        $dst reformat($src);
                        // nachher anzeigen
                        display($dst); 
                        Um $src, wie hier benötigt, als String und nicht als Array von Zeilen zu erhalten, kannst du statt file() die Funktion file_get_contents() verwenden.


                        Selbstverständlich geht das Ganze auch ohne Reguläre Ausdrücke.
                        Als Beispiel die Variante mit fopen(), fgets() und fclose():

                        PHP-Code:
                        // file zeilenweise einlesen
                        $fh fopen('dateiname''r');
                        $lines = array (); // enthält die neu formatierten Zeilen
                        $empty_lines 0;  // zählt die eingelesenen Leerzeilen vor jeder Text-Zeile
                        $is_start TRUE// der gleiche Trick wie weiter oben
                        // fgets() liest eine Datei Zeile für Zeile ein und gibt
                        // FALSE zurück, wenn das Dateiende erreicht ist
                        while (FALSE !== $line fgets($fh)) {
                          
                        $line rtrim($line); // Whitespaces und Zeilenumbrüche entfernen
                          
                        if (!isset($line[0])) {  // wir haben eine Leerzeile
                            
                        ++$empty_lines// Leerzeile zählen
                            // aber KEINE Leerzeile hinzufügen
                          
                        }
                          else {
                            if (
                        $is_start) {
                              
                        $is_start FALSE;
                              
                        // KEINE Leerzeilen hinzufügen
                            
                        }
                            
                        // Regel 1 und 2
                            
                        elseif ($empty_lines == || $empty_lines == 1) {
                              
                        $lines[] = ''// eine Leerzeile hinzufügen
                            
                        }
                            
                        // sonst Regel 3
                            
                        else {
                              
                        $lines[] = ''// zwei Leerzeilen
                              
                        $lines[] = ''// hinzufügen
                            
                        }
                            
                        $lines[] = $line// eine Textzeile hinzufügen
                            
                        $empty_lines 0// den Leerzeilen-Zähler wieder zurücksetzen
                          
                        }
                        }
                        fclose($fh); // datei(handle) schließen 
                        Leerzeilen am Ende werden einfach ignoriert, brauchen also nicht gesondert
                        behandelt zu werden.

                        Hier liegt der umformatierte Text nach dem Einlesen als Array von Zeilen in $lines vor.
                        Zuletzt geändert von fireweasel; 28.05.2009, 10:35.
                        Klingon function calls do not have “parameters”‒they have “arguments”‒and they always win them!

                        Kommentar


                        • #13

                          Kommentar


                          • #14
                            Das werde ich durcharbeiten.

                            Dank und Gruß

                            Estrela
                            Denk positiv.

                            Kommentar


                            • #15
                              Hallo fireweasel.

                              Auf den ersten Blick hatte ich stille Bedenken, ob ich das zum laufen bekomme. Aber nein, auch und gerade für Beginner sehr zu empfehlen als kleiner Einstieg oder Aufwärmübung ;-) in den Bereich der regulären Ausdrücke.

                              Super arbeit. Du hast Dich ja richtig dem Thema angenommen und eine 1a Lösung präsentiert und diese auch noch dankenswerterweise gut dokumentiert.

                              Nicht so leicht zu verstehen ist das mit der Rückreferenzierung und der Zählung der Treffer.
                              Ich halte zu dem Thema erst mal fest, das jede Klammer durchnummeriert ansprechbar ist.

                              Weitere Informationen bzg.der Zeichen innerhalb des Ausdrucks habe ich im Manual hier auf Englisch und für die Modifikatoren außerhalb des Suchmusters hier gefunden.

                              Bemerkenswert finde ich deine Antizipationsfähigkeit bezüglich der Funktionsfähigkeit des Scripts, um unter möglichst vielen Umständen ( /\s*?)die Aufgabe zu erfüllen. Super Eigenschaft!

                              Zu meinen Arrays, die ich oben eingerichtet habe, möchte ich noch sagen, das damit die Position der einzelnen Zeilenarten( Titel, Leerzeile, Strophenzeile) bekannt und damit dann die Startposition und Länge der Strophen berechenbar geworden sind. In so fern währen sie schon nützlich gewesen, währe nicht ein besserer Weg in Richtung Ziel erkennbar geworden.

                              Danke für deine allgemeinen und speziellen Ausführungen, die das Thema auf dieser Stufe schön ausgeleuchtet haben.

                              Und ich würde mich freuen, auch in meinem nächsten Beitrag, in dem es um die abschließende Formatierung der Dateien geht
                              ( hättest Du wahrscheinlich in einem Schritt gemacht, ich möchte aber wegen eventueller nachträglicher Änderungen keinen hochkomplexen Code),
                              wieder von dir zu hören.
                              Wenn es deine Zeit zuläßt, vielleicht nicht als fertige Lösung, sondern als geduldiger Ratgeber auf dem Weg zum Ziel.


                              @asp2php

                              Danke auch dir nochmal für deine geduldige Erläuterung, die zur Verbesserung meines grundlegenden Verständnisses beigetragen hat.



                              Gruß

                              Estrela
                              Denk positiv.

                              Kommentar

                              Lädt...
                              X