Zitat:
Zitat von Estrela
...
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:
Zitat:
|
Oder kann ich schon direkt nach einer Regel in der Schleife die überzähligen Leerzeilen löschen?
|
Siehe auch etwas weiter unten im Text ...
Zitat:
... 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. ;-)
Zitat:
|
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 == 0 || $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.