Regex ueber Multilinestring

Regex ueber Multilinestring

am 15.08.2007 16:59:10 von Peter Velan

Hallo,

Input ist File (mit DOS-EOL = 0D0A) und Inhalt á la:

1.english text,
could spread over more than on line
a third line is possible, numbers 1234., too
1.deutscher Text,
kann sich über mehrere Zeilen erstrecken
eine dritte Zeile ist möglich, Zahlen 1234. auch
2.another englisch text, only one line
2.noch'n deutscher Text, nur eine Zeile
3.engl...
3.dt...

Nachdem ich mit Einlesen und gleich Zerpflücken auf die Nase gefallan
bin, habe ich komplette File in eine Variable $chunk gezogen und will
nun aus $chunk so lange rausschnibbeln bis $chunk leer wird. Mein
(erfolgloser) Ansatz:

while( $chunk ) {
$chunk =~ m/^(\d+?)\.(.*?)\x0d\x0a(\d+?)\.(.*?)\x0d\x0a(.*)$/s;
# $1 $2 $3 $4 $5
# $1 = num. engl.
# $2 = text engl.
# $3 = num. dt.
# $4 = text dt.
# verarbeitung
$chunk = $5;
}

Was ich auch anstelle, in $2 ist stets der komplette Reststring drin.
Warum matcht das "\x0d\x0a" nicht?

(Die File habe ich mit einem Hex-Editor überprüft, es sind 0D0A
Zeilenenden).

Für Tipps sehr empfänglich,
Peter

Re: Regex ueber Multilinestring

am 15.08.2007 17:18:40 von Mirco Wahab

Peter Velan wrote:
> while( $chunk ) {
> $chunk =~ m/^(\d+?)\.(.*?)\x0d\x0a(\d+?)\.(.*?)\x0d\x0a(.*)$/s;
> # $1 $2 $3 $4 $5
> # $1 = num. engl.
> # $2 = text engl.
> # $3 = num. dt.
> # $4 = text dt.
> # verarbeitung
> $chunk = $5;
> }
> Was ich auch anstelle, in $2 ist stets der komplette Reststring drin.
> Warum matcht das "\x0d\x0a" nicht?
> (Die File habe ich mit einem Hex-Editor überprüft, es sind 0D0A
> Zeilenenden).

Hmmm, wäre interessant zu erfahren, was Du überhaupt
machen willst. Mit Deinem Regex komme ich nicht klar,
ich verstehe nicht, was er machen soll.

Ich nenne mal "meine Version" dessen, was ich
verstanden habe:

....

my $text = q{
1.english text,
could spread over more than on line
a third line is possible, numbers 1234., too
1.deutscher Text,
kann sich über mehrere Zeilen erstrecken
eine dritte Zeile ist möglich, Zahlen 1234. auch
2.another englisch text, only one line
2.noch'n deutscher Text, nur eine Zeile
3.engl...
3.dt...
};

my $regex=qr{ [\r\n]
(\d+) \. (.*?) [\r\n]
\1 \. ( (?:.*? (?![\r\n]\d))+ )
}sx;

while( $text =~ /$regex/g ) {
print "Nummer:$1\nTitle:$2\n$3\n", '~'x20, "\n"
}

....

Das ist ein wenig kompliziert zusammengeschustert,
aber ich wollte nachbilden, was ich denke was du
meintest.

Dass in Deinem Text, nachdem Perl ihn eingelesen hat,
noch \0d drin sein soll, ist nur möglich, wenn
mit dem Einlesen etwas ungeöhnliches passierte.
Normalerweise (imho) konvertiert Perl bei E/A.

Viele Grüße

Mirco

Re: Regex ueber Multilinestring

am 15.08.2007 18:04:26 von ekkehard.horner

Peter Velan schrieb:
> Hallo,
>=20
> Input ist File (mit DOS-EOL =3D 0D0A) und Inhalt =E1 la:
>=20
Nach dem Einlesen sind die \r aber weg, falls nicht binmode() verwendet
wurde. Als Evidenz (perldoc -f binmode):

The operating system, device drivers, C libraries, and Perl
run-time system all work together to let the programmer treat a
single character ("\n") as the line terminator, irrespective of
the external representation. On many operating systems, the
native text file representation matches the internal
representation, but on some platforms the external
representation of "\n" is made up of more than one character.

> 1.english text,
> could spread over more than on line
> a third line is possible, numbers 1234., too
> 1.deutscher Text,
> kann sich über mehrere Zeilen erstrecken
> eine dritte Zeile ist möglich, Zahlen 1234. auch
> 2.another englisch text, only one line
> 2.noch'n deutscher Text, nur eine Zeile
> 3.engl...
> 3.dt...
>=20
> Nachdem ich mit Einlesen und gleich Zerpflücken auf die Nase gefallan=

> bin, habe ich komplette File in eine Variable $chunk gezogen und will
> nun aus $chunk so lange rausschnibbeln bis $chunk leer wird. Mein
> (erfolgloser) Ansatz:
>=20
> while( $chunk ) {
> $chunk =3D~ m/^(\d+?)\.(.*?)\x0d\x0a(\d+?)\.(.*?)\x0d\x0a(.*)$/s;
> # $1 $2 $3 $4 $5
> # $1 =3D num. engl.
> # $2 =3D text engl.
> # $3 =3D num. dt.
> # $4 =3D text dt.
> # verarbeitung
> $chunk =3D $5;
> }
>=20
> Was ich auch anstelle, in $2 ist stets der komplette Reststring drin.
> Warum matcht das "\x0d\x0a" nicht?
>=20
Weil kein \x0d in $chunk ist

> (Die File habe ich mit einem Hex-Editor überprüft, es sind 0D0A
> Zeilenenden).
>=20
> Für Tipps sehr empfänglich,
> Peter

Mein Tipp ist: split (mit capture des "n." Separators verwenden:

my $sTFSpec =3D './splitdemo.txt';
my $sTxt =3D slurpFile( $sTFSpec );
printf "Anzahl NL in $sTFSpec: %d.\n", $sTxt =3D~ tr/\n/\n/;
printf "Anzahl CR in $sTFSpec: %d.\n", $sTxt =3D~ tr/\r/\r/;
my $reSplit =3D qr{ \n? # das dumme \n vor fast allen n.
^ # zeilenanfang
( # separator auch ins resultat
\d+ # ziffernfolge
\. # punkt
)
}xms;
my @Parts =3D split $reSplit, $sTxt;
shift @Parts; # erstes Element muss leer sein ('links' vom Separator)
for( my $nIdx =3D 0; $nIdx < scalar @Parts; $nIdx +=3D 4 )
{
# printf "%s\n%s\n%s\n%s\n", @Parts[ $nIdx .. ($nIdx + 3) ];
my $sNum =3D $Parts[ $nIdx ];
if( $sNum ne $Parts[ $nIdx + 2 ] )
{ die "bingo!"
}
my $sEngl =3D $Parts[ $nIdx + 1 ];
$sEngl =3D~ tr/\n/ /;
my $sGerm =3D $Parts[ $nIdx + 3 ];
$sGerm =3D~ tr/\n/ /;
printf "Nr.: %s\n Engl: |%s|\n Deu: |%s|\n", $sNum, $sEngl, $s=
Germ;
}

Re: Regex ueber Multilinestring

am 15.08.2007 18:13:50 von Bjoern Hoehrmann

* Peter Velan wrote in de.comp.lang.perl.misc:
>Input ist File (mit DOS-EOL = 0D0A) und Inhalt á la:
>
>1.english text,
>could spread over more than on line
>a third line is possible, numbers 1234., too
>1.deutscher Text,
>kann sich über mehrere Zeilen erstrecken
>eine dritte Zeile ist möglich, Zahlen 1234. auch
>2.another englisch text, only one line
>2.noch'n deutscher Text, nur eine Zeile
>3.engl...
>3.dt...
>
>Nachdem ich mit Einlesen und gleich Zerpflücken auf die Nase gefallan
>bin, habe ich komplette File in eine Variable $chunk gezogen und will
>nun aus $chunk so lange rausschnibbeln bis $chunk leer wird. Mein
>(erfolgloser) Ansatz:

Ich seh das Problem mit Zeilenweiser Verarbeitung nicht, aber was
spricht gegen ein einfaches split /^\d+\./m, $string (mit Klammern
in dem regulären Ausdruck wenn du Wert auf die Nummern legst). Das
zeilenweise Lesen wäre einfach

while( ) {
if (/^\d+\./) {
# neuen Eintrag aufmachen
} else {
# zum aktuellen Eintrag hinzufügen
}
}

In beiden Fällen hast du dann ungerade und gerade Indices um die
Sprache festzustellen.
--
Björn Höhrmann · mailto:bjoern@hoehrmann.de · http://bjoern.hoehrmann.de
Weinh. Str. 22 · Telefon: +49(0)621/4309674 · http://www.bjoernsworld.de
68309 Mannheim · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/

Re: Regex ueber Multilinestring

am 15.08.2007 18:23:21 von Peter Velan

am 15.08.2007 17:18 schrieb Mirco Wahab:
> Peter Velan wrote:
>> while( $chunk ) {
>> $chunk =~ m/^(\d+?)\.(.*?)\x0d\x0a(\d+?)\.(.*?)\x0d\x0a(.*)$/s;
>> # $1 $2 $3 $4 $5
>> # $1 = num. engl.
>> # $2 = text engl.
>> # $3 = num. dt.
>> # $4 = text dt.
>> # verarbeitung
>> $chunk = $5;
>> }
>> Was ich auch anstelle, in $2 ist stets der komplette Reststring drin.
>> Warum matcht das "\x0d\x0a" nicht?
>> (Die File habe ich mit einem Hex-Editor überprüft, es sind 0D0A
>> Zeilenenden).
>
> Hmmm, wäre interessant zu erfahren, was Du überhaupt
> machen willst.

Sorry für die verwirrende Erklärung. Also: Ich muß aus einer DOS-File
jeweils zwei Stringpaare extrahieren (dt./engl. Korrespondenzen). Wie im
Beispiel angegeben, kann sowohl dt. als auch engl. Text "innere"
Zeilenumbrüche haben, die erhalten bleiben müssen.

> Mit Deinem Regex komme ich nicht klar,
> ich verstehe nicht, was er machen soll.

- ^ Stringanfang
- bel.Zahl $1
- \. wegwerfen
- alles (inkl innerer Zeilenumbrüche) $2
bis zum Muster Umbruch+bel.Zahl ($3)
- \. wegwerfen
- alles (inkl innerer Zeilenumbrüche) $4
bis zum Muster Umbruch+bel.Zahl
^^^^^^^^^
ist mein Fehler, sehe ich gerade!

Rest in $5 wird neuer Input-String

> Ich nenne mal "meine Version" dessen, was ich
> verstanden habe:
>
> ...
>
> my $text = q{
> 1.english text,
> could spread over more than on line
> a third line is possible, numbers 1234., too
> 1.deutscher Text,
> kann sich über mehrere Zeilen erstrecken
> eine dritte Zeile ist möglich, Zahlen 1234. auch
> 2.another englisch text, only one line
> 2.noch'n deutscher Text, nur eine Zeile
> 3.engl...
> 3.dt...
> };
>
> my $regex=qr{ [\r\n]
> (\d+) \. (.*?) [\r\n]
> \1 \. ( (?:.*? (?![\r\n]\d))+ )
> }sx;

Werde ich mir *ganz* genau ansehen, sieht spannend aus.

> Das ist ein wenig kompliziert zusammengeschustert,
> aber ich wollte nachbilden, was ich denke was du
> meintest.

Danke für den Versuch mein wirres Denken nachzuvollziehen :-)

> Dass in Deinem Text, nachdem Perl ihn eingelesen hat,
> noch \0d drin sein soll, ist nur möglich, wenn
> mit dem Einlesen etwas ungeöhnliches passierte.
> Normalerweise (imho) konvertiert Perl bei E/A.

*YEP* - hier schlummert das Häschen im Pfeffer!

Hiermit habe ich meinen Text eingelesen:

open( INFILE, "< dosfile.txt" ) or die $!;
$/ = undef;
my $chunk = ;
close( INFILE );

.... und wie du völlig richtig schreibst, ist im $chunk tatsächlich kein
\x0d mehr drin! Deswegen konnte "\x0a\x0d" nicht matchen!

Ich dachte, wenn ich eine File mit "$/ = undef" einlese, dass dann gar
nichts angefasst/konvertiert wird.

Vielen Dank, hast mir sehr geholfen!

Peter

Re: Regex ueber Multilinestring

am 15.08.2007 20:43:45 von Peter Velan

am 15.08.2007 18:04 schrieb ekkehard.horner:
> Peter Velan schrieb:
>> Hallo,
>>
>> Input ist File (mit DOS-EOL = 0D0A) und Inhalt á la:
>>
> Nach dem Einlesen sind die \r aber weg, falls nicht binmode() verwendet
> wurde.

Nein, kein binmode hier ... aber völlig korrekt die \r sind dann eben
futsch und ...

>> Was ich auch anstelle, in $2 ist stets der komplette Reststring drin.
>> Warum matcht das "\x0d\x0a" nicht?
>
> Weil kein \x0d in $chunk ist
^^^^^^^^^
das ist genau mein zentrales Verständnisproblem gewesen!

> Mein Tipp ist: split (mit capture des "n." Separators verwenden:
>
> my $sTFSpec = './splitdemo.txt';
> my $sTxt = slurpFile( $sTFSpec );
> printf "Anzahl NL in $sTFSpec: %d.\n", $sTxt =~ tr/\n/\n/;
> printf "Anzahl CR in $sTFSpec: %d.\n", $sTxt =~ tr/\r/\r/;
> my $reSplit = qr{ \n? # das dumme \n vor fast allen n.
> ^ # zeilenanfang
> ( # separator auch ins resultat
> \d+ # ziffernfolge
> \. # punkt
> )
> }xms;
> my @Parts = split $reSplit, $sTxt;
> shift @Parts; # erstes Element muss leer sein ('links' vom Separator)
> for( my $nIdx = 0; $nIdx < scalar @Parts; $nIdx += 4 )
> {
> # printf "%s\n%s\n%s\n%s\n", @Parts[ $nIdx .. ($nIdx + 3) ];
> my $sNum = $Parts[ $nIdx ];
> if( $sNum ne $Parts[ $nIdx + 2 ] )
> { die "bingo!"
> }
> my $sEngl = $Parts[ $nIdx + 1 ];
> $sEngl =~ tr/\n/ /;
> my $sGerm = $Parts[ $nIdx + 3 ];
> $sGerm =~ tr/\n/ /;
> printf "Nr.: %s\n Engl: |%s|\n Deu: |%s|\n", $sNum, $sEngl, $sGerm;
> }

Na, vielen Dank! Da habe ich auch wieder ein schönes Stück zum üben ;-)
Ich hätte ich mir split besser ansehen sollen.

Inzwischen habe ich es "irgendwie" hinbekommen (wenn auch nicht
besonders elegant), beim nächsten mal wirds dann sicher besser.

Danke für die Tipps,
Peter

Re: Regex ueber Multilinestring

am 15.08.2007 20:47:04 von Peter Velan

am 15.08.2007 18:13 schrieb Bjoern Hoehrmann:
> * Peter Velan wrote in de.comp.lang.perl.misc:
>>Input ist File (mit DOS-EOL = 0D0A) und Inhalt á la:
>>
>>1.english text,
>>could spread over more than on line
>>a third line is possible, numbers 1234., too
>>1.deutscher Text,
>>kann sich über mehrere Zeilen erstrecken
>>eine dritte Zeile ist möglich, Zahlen 1234. auch
>>2.another englisch text, only one line
>>2.noch'n deutscher Text, nur eine Zeile
>>3.engl...
>>3.dt...
>>
>>Nachdem ich mit Einlesen und gleich Zerpflücken auf die Nase gefallan
>>bin, habe ich komplette File in eine Variable $chunk gezogen und will
>>nun aus $chunk so lange rausschnibbeln bis $chunk leer wird. Mein
>>(erfolgloser) Ansatz:
>
> Ich seh das Problem mit Zeilenweiser Verarbeitung nicht,

Das sagst du ;-) Ich habe mich jedenfalls ziemlich verheddert und dann
diesen Weg über Einlesen als kompletter String gewählt.

> aber was
> spricht gegen ein einfaches split /^\d+\./m, $string (mit Klammern
> in dem regulären Ausdruck wenn du Wert auf die Nummern legst). Das
> zeilenweise Lesen wäre einfach
>
> while( ) {
> if (/^\d+\./) {
> # neuen Eintrag aufmachen
> } else {
> # zum aktuellen Eintrag hinzufügen
> }
> }

Danke, ich fand es ja anfangs auch "natürlicher" gleich beim
zeilenweisen Einlesen die Arbeit in der selben schleife zu erledigen,
aber ... siehe oben.

Danke,
Peter

Re: Regex ueber Multilinestring

am 16.08.2007 14:59:28 von Mirco Wahab

Peter Velan wrote:
> am 15.08.2007 17:18 schrieb Mirco Wahab:
>> Hmmm, wäre interessant zu erfahren, was Du überhaupt
>> machen willst.
>
> Sorry für die verwirrende Erklärung. Also: Ich muß aus einer DOS-File
> jeweils zwei Stringpaare extrahieren (dt./engl. Korrespondenzen). Wie im
> Beispiel angegeben, kann sowohl dt. als auch engl. Text "innere"
> Zeilenumbrüche haben, die erhalten bleiben müssen.

OK,

>> Mit Deinem Regex komme ich nicht klar,
>> ich verstehe nicht, was er machen soll.
> ...
> ...
> Rest in $5 wird neuer Input-String

Aha, das habe ich noch nie gesehen und ich denke,
das braucht man so auch nicht zu machen.

> Hiermit habe ich meinen Text eingelesen:
> open( INFILE, "< dosfile.txt" ) or die $!;
> $/ = undef;
> my $chunk = ;
> close( INFILE );

OK. Mich würde trotzdem interessieren, wie
Du die Lösung des Problems "irgendwie"
hinbekommen hast, da imho die Aufgaben-
stellung alles andere als trivial ist.

Ich habe mal die von Dir geschilderte
Aufgabenstellung selbst versucht und
muss zugeben, eine Weile am Regex herum-
gedoktort zu haben (Letztlich habe ich
mich für den /ms-Modus entschieden,
da der Regex etwas einfacher wird.)

> Ich dachte, wenn ich eine File mit "$/ = undef" einlese, dass dann gar
> nichts angefasst/konvertiert wird.

Zu dieser Sache gibt es so etwas wie eine "idiomatische"
Lösung, das sähe etwa so aus:

...
my $fname = 'dosfile.txt';
open my $fh, '<', $fname or die $!;

my $chunk; { local $/; $chunk = <$fh> }
close $fh;
...

d.h. auch, BARWEORD-Filehandles sind ein
Überbleibsel aus der Perl4-Zeit.


Die eigentliche Suche auf $chunk hätte ich
so gelöst:

...
my $rg = qr{ ^ (\d+)\. ( .+? ) # (Ziffernfolge), (EN)
^ \1\. ( (?: .(?!^\d) )+ ) # Ziffernfolge, (DE)
}msx;

my @trans;
push @trans, [$1, $2, $3] while $chunk =~ /$rg/g;

print map "Num:\t$_->[0] \n EN:\t$_->[1] DE:\t$_->[2]\n\n", @trans;
...

Mit der "\1" stelle ich sicher, dass die *selbe* Zahl (aus $1)
nochmal verwendet wird.

Vile Grüße

Mirco

Re: Regex ueber Multilinestring

am 17.08.2007 16:16:34 von Peter Velan

am 16.08.2007 14:59 schrieb Mirco Wahab:
> Peter Velan wrote:
>> Rest in $5 wird neuer Input-String
>
> Aha, das habe ich noch nie gesehen und ich denke,
> das braucht man so auch nicht zu machen.

.... wie war das mit "... more than one way ..." ;-)

>> Hiermit habe ich meinen Text eingelesen:
>> open( INFILE, "< dosfile.txt" ) or die $!;
>> $/ = undef;
>> my $chunk = ;
>> close( INFILE );
>
> OK. Mich würde trotzdem interessieren, wie
> Du die Lösung des Problems "irgendwie"
> hinbekommen hast, da imho die Aufgaben-
> stellung alles andere als trivial ist.

Also hier meine (vermutlich krumme und unelegante Lösung):

-----8<-----
#!c:/perl/bin -w
# ^^^ hier f. Win, anpassen f. Linux
use strict;

my $save_eor = $/;
open( INFILE, "< dosfile.txt" ) or die $!;
$/ = undef;
my $chunk = ;
close( INFILE );
$/ = $save_eor;

# muss zusätzlich an $chunk was anhängen, damit auch das
# letzte gültige Stringpaar verarbeitet wird
#
$chunk .= "99999\.en-dummy\n99999\.de-dummy\n";

while( $chunk ) {
$chunk =~
m/
^ # Stringanfang
(\d+?)\. # $1= Pos# en-Text, Punk wegwerfen
(.*?) # $2= en-Text, geht solange bis ...
\n(\d+?)\. # ... Z.Umbruch + Zahl + Punkt; $3= Pos# dt-Text
(.*?) # $4= dt-Text, geht so lange bis ...
\n(\d+?)\. # ... Z.Umbruch + Zahl + Punkt; $5= Pos# next en-Text
(.*)$ # $6= Reststring
/sx;

my $en_num = $1; # extrahierten Werte
my $en_txt = $2; # für spätere Verarbeitung sichern
my $ge_num = $3; # ...
my $ge_txt = $4; # ...

last unless( defined( $5 ) and defined( $6 ) ); # end-of-loop checker

$chunk = "$5\.$6"; # neuen $chunk zusammenschustern

# Druck (debug only)

print "==========================\n";
print "en = $en_num ***$en_txt***\n";
print "ge = $ge_num ***$ge_txt***\n";
}
----->8-----

(Ich hoffe ich habe keinen Fehler bei der Übernahme aus meinem
Originalskript eingebaut.)

Auf irgendwelche Optimierungen muß ich bei diesem Job nicht achten: Ob
das Skript 'ne halbe oder 3 Sekunden rattert ist hier wirklich wurscht.

> Ich habe mal die von Dir geschilderte
> Aufgabenstellung selbst versucht und
> muss zugeben, eine Weile am Regex herum-
> gedoktort zu haben (Letztlich habe ich
> mich für den /ms-Modus entschieden,
> da der Regex etwas einfacher wird.)

Mit /m, /s oder /ms habe ich so meine liebe Not. Habe den entspr.
Abschnitt der Doku schon oft gelesen, aber so richtig verstanden habe
ich es vermutlich immer noch nicht. :-(

>> Ich dachte, wenn ich eine File mit "$/ = undef" einlese, dass dann gar
>> nichts angefasst/konvertiert wird.
>
> Zu dieser Sache gibt es so etwas wie eine "idiomatische"
> Lösung, das sähe etwa so aus:
> ...
> my $fname = 'dosfile.txt';
> open my $fh, '<', $fname or die $!;
>
> my $chunk; { local $/; $chunk = <$fh> }
> close $fh;

Uij, schee! Habe bisher, wenn ich inkl. Zeilenumbrüchen einlesen wollte,
mit Zwischenspeicherung von "$/" gearbeitet (s.o. "$save_eor"), damit
der globale Wert von $/ nicht verbogen wird.

> d.h. auch, BARWEORD-Filehandles sind ein
> Überbleibsel aus der Perl4-Zeit.

Ah, Danke für den Hinweis mit dem Handle.

> Die eigentliche Suche auf $chunk hätte ich
> so gelöst:
>
> ...
> my $rg = qr{ ^ (\d+)\. ( .+? ) # (Ziffernfolge), (EN)
> ^ \1\. ( (?: .(?!^\d) )+ ) # Ziffernfolge, (DE)
> }msx;
>
> my @trans;
> push @trans, [$1, $2, $3] while $chunk =~ /$rg/g;
>
> print map "Num:\t$_->[0] \n EN:\t$_->[1] DE:\t$_->[2]\n\n", @trans;
> ...
>
> Mit der "\1" stelle ich sicher, dass die *selbe* Zahl (aus $1)
> nochmal verwendet wird.

Hmm, ich bin vermutlich schon zu alt um noch akzentfreies perlianisch zu
zu erlernen, werde mir deine Lösung aber sicher genauer ansehen. An
Backtraces mit \1 und so, habe ich mich noch nicht herangetraut.

Vielen Dank und Gruß,
Peter