Schrift
[thread]12860[/thread]

Mehrere verschachtelte Begriffe in einem String verändern



<< >> 9 Einträge, 1 Seite
Gast Gast
 2008-12-05 09:42
#116828 #116828
Hallo,

ich möchte in einem String $zeile (der später in eine html-Datei geschrieben wird) mehrere verschiedene Begriffe mit einem html-tag (z.B. <b></b>) versehen.

Der String lautet: "Wir alle sind auf den Witz hereingefallen." Gesucht werden sollen alle Vorkommen der Begriffe "fall" "alle" "len". Jeder Begriff soll am Ende mit "<b>fall</b>" bzw. "<b>alle</b>" bzw. "<b>len</b>" im String erscheinen. Das Endergebnis soll also lauten (mit Beibehaltung der Groß- und Kleinschreibung):

"Wir <b>alle</b> sind in der <b><b>Fall</b>e</b> auf den Witz hereinge<b>fal<b>l</b>en</b>." (ich weiß, das ist unsauber html-code, aber es ist nur für den privaten Hausgebrauch und würde mir so genügen).

Das Problem sind die verschachtelten Suchbegriffe. Ich habe viele verschiedene Lösungswege versucht, u.a. den nachstehenden, auch mit verschiedenen "substr"-Versuchen - aber es gelingt mir einfach nicht. Hat jemand eine Lösung?

Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$bstart = "<b>";
$bende = "</b>";


foreach $suchbegriff (@namen)
{
foreach $zeile (@allezeilen)
{
if ($zeile =~ /$suchbegriff/ig) {
$zeile =~ s/$suchbegriff/$bstart$&$bende/ig;
$zeile = "$zeile \n";
push(@begriffgefunden, $zeile);
}
}
}


Danke im voraus!

Michael
flyniguana
 2008-12-05 14:21
#116840 #116840
User since
2008-12-05
1 Artikel
BenutzerIn
[default_avatar]
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$chomp=($line);


@a=split(/;/,$line);
push(@record, @a);



foreach (@record)
{
  $i++;


  if ($i eq 1){
  push (@string_part_one,$_);
  }
  if ($i eq 1){
  push (@string_part_two,$_);
  }


so wuerdest Du zumindest schonmal die jeweilige Zeile aufteilen koennen und diese auch fuer eine Mehrfachbenutzung in Arrays speichern.
Diese muesstest Du dann je nachdem wie es gewuenscht ist inhaltlich z.B. mit einer If abfrage pruefen ob dein String vorkommt. Tut er das, dann ergaenzt Du den zuletzt genutzten Array um <b>INHALT</b>.

Hab grad leider nicht viel Zeit. Kann das aber gerne wenn Du moechtest heute Abend nochmal fuer dich coden.

Sicher nicht ganz elegant geloest, bin leider noch recht neu in Perl, aber hoffentlich eine Hilfe fuer Dich.
RegEx musst Du nur noch anpassen. Habe den Code zur ; Entfernung und Aufteilung einer CSV Datei genutzt.


Gast Gast
 2008-12-05 14:55
#116843 #116843
ungetestet:
Code (perl): (dl )
$zeile ~=s/(?<=\W)(fall\w*|alle)(?=\W)/$bstart$1$bende/gsi;
Gast Gast
 2008-12-05 16:34
#116851 #116851
besser so:
Code: (dl )
$zeile=~s/(f?alle?n?)/$bstart$1$bende/gsi;
Gast Gast
 2008-12-05 17:34
#116857 #116857
Gast+2008-12-05 13:55:42--
ungetestet
Code (perl): (dl )
$zeile ~=s/(?<=\W)(fall\w*|alle)(?=\W)/$bstart$1$bende/gsi;


Das Problem an dieser Zeile ist:

Wenn "fall" in 'hereingefallen' gefunden wird, wird daraus "hereinge<b>fall</b>en". Jetzt wird aber "alle" nicht mehr gefunden, weil der html-Tag "alle" trennt - gleiches gilt für "len".
Das ist mein Problem.

Und die Suchbegriffe können sich ändern und sind unvorhersehbar.

Michael
murphy
 2008-12-05 18:36
#116860 #116860
User since
2004-07-19
1776 Artikel
HausmeisterIn
[Homepage]
user image
Gast+2008-12-05 16:34:00--
[...]
Wenn "fall" in 'hereingefallen' gefunden wird, wird daraus "hereinge<b>fall</b>en". Jetzt wird aber "alle" nicht mehr gefunden, weil der html-Tag "alle" trennt - gleiches gilt für "len".
Das ist mein Problem.
[...]


Ich würde ja mal die Anforderungen an das Resultat überdenken: Es ist in HTML – bzw. generell in SGML – zwar möglich, Tags zu schachteln, aber nicht überlappende Tags zu erzeugen.

Wenn man in "hereingefallen" also sowohl "fall" als auch "alle" markieren möchte, dann kann man das unmöglich so tun, dass beide markierten Strings jeweils der Inhalt eines eigenen Tags sind. Man könnte hingegen sinnvoll so markieren, dass bei "hereingefallen" mit Suchbegriffen "fall" und "alle" im Endeffekt "hereinge<b>falle</b>n" herauskommt, die Markierungen also quasi verschmolzen werden.

Um das zu realisieren, würde ich nicht direkt mit dem Substitutionsoperator arbeiten sondern mit dem Matchoperator die Positionsintervalle aller Treffer für die Suchbegriffe bestimmen (Stichworte pos, @-, @+), diese Intervalle falls sie überlappen oder aneinander angrenzen vereinigen und schließlich um den Text in jedem Intervall herum mittels substr die Markierungstags einsetzen.
When C++ is your hammer, every problem looks like your thumb.
Gast Gast
 2008-12-05 19:08
#116861 #116861
Nachdem ich entdeckt habe, dass die Hervorhebung verschachtelter Suchbegriffe auch im MoinMoin-Wiki nicht wie gewünscht funktioniert, habe ich das Problem -für mich (!)- so gelöst:
Ich sortiere die Suchbegriffe im Array (z.B. "gefall" "alle" "len") zuerst nach der Länge und ergänze dann mit den html-Codes:

Code: (dl )
1
2
3
4
foreach $suchbegriff (@namen)
{
$zeile =~ s/$suchbegriff/$bstart$&$bende/ig;
}


Dann wird aus "Wir alle sind auf den Witz hereingefallen." -> Wir <bstart">alle</bende> sind auf den Witz herein<bstart>gefall</bende>en. Damit bekomme ich zwar nicht alle Suchbegriffe in der html-Datei hervorgehoben, aber für meinen Hausgebrauch reicht es.

Den Hinweis von murphy mit den Positionsintervallen werde ich mal in einer ruhigen Minute nachgehen.

Danke für alle Hinweise.

Michael
Gast Gast
 2008-12-05 19:13
#116862 #116862
Gast+2008-12-05 18:08:54--
Code: (dl )
1
2
3
4
foreach $suchbegriff (@namen)
{
$zeile =~ s/$suchbegriff/$bstart$&$bende/ig;
}

Dann wird aus "Wir alle sind auf den Witz hereingefallen." -> Wir <bstart>alle</bende> sind auf den Witz herein<bstart>gefall</bende>en.


Richtig muss es natürlich heißen:

Code: (dl )
1
2
3
4
5
6
7
8
$bstart = "<b>";
$bende = "</b>";


foreach $suchbegriff (@namen)
{
$zeile =~ s/$suchbegriff/$bstart$&$bende/ig;
}


Dann wird aus "Wir alle sind auf den Witz hereingefallen." -> Wir <b">alle</b> sind auf den Witz herein<b>gefall</b>en.

Michael
murphy
 2008-12-05 22:14
#116863 #116863
User since
2004-07-19
1776 Artikel
HausmeisterIn
[Homepage]
user image
Gast+2008-12-05 18:08:54--
[...] Den Hinweis von murphy mit den Positionsintervallen werde ich mal in einer ruhigen Minute nachgehen. [...]


Das ist eine ganz einfache Sache. Hier mal eine beispielhafte Implementation – nicht sehr effizient, da man die Intervalle schlauer speichern könnte, aber sie funktioniert einwandfrei:
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#
# Mark ranges in a string that match certain regular expressions
#
use 5.010;

use strict;
use warnings;

use List::Util qw/reduce/;
use List::MoreUtils qw/part/;

use constant {
    PATTERNS   => [ qr/gefall/, qr/alle/, qr/len/ ],
    MARK_START => '<b>',
    MARK_END   => '</b>'
};

sub insert_range(\@$$) {
    my ($rng, $beg, $end) = @_;

    my ($dsj, $ovl) = part {
        ($_->[0] >= $beg and not $_->[0] >= $end) or
            ($_->[1] >= $beg and not $_->[1] >= $end) or
            0;
    } @{$rng};

    $dsj //= [ ];
    $ovl //= [ ];

    foreach my $_ (@{$ovl}) {
        $beg = $_->[0] if ($_->[0] < $beg);
        $end = $_->[1] if ($_->[1] > $end);
    }

    @{$rng} = sort {
        $a->[0] <=> $b->[0];
    } @{$dsj}, [ $beg, $end ];
}

sub find_ranges($) {
    my ($str, @pat) = @_;
    my @rng = ();

    foreach my $pat (@{PATTERNS()}) {
        while ($str =~ m/$pat/g) {
            insert_range @rng, $-[0], $+[0];
        }
    }

    return @rng;
}

sub split_ranges($@) {
    my ($str, @rng) = @_;
    my ($pos, @pcs) = (0);
    my $_;

    foreach (@rng) {
        if ($pos < $_->[0]) {
            push @pcs, [ 0, substr $str, $pos, $_->[0] - $pos ];
        }
        push @pcs, [ 1, substr $str, $_->[0], $_->[1] - $_->[0] ];
        $pos = $_->[1];
    }

    if ($pos < length($str)) {
        push @pcs, [ 0, substr $str, $pos, length($str) - $pos ];
    }

    return @pcs;
}

sub mark_ranges(@) {
    reduce {
        if ($b->[0]) {
            $a . MARK_START . $b->[1] . MARK_END;
        }
        else {
            $a .= $b->[1];
        }
    } '', @_;
}

my $_;
while (<DATA>) {
    chomp;
    $_ = mark_ranges split_ranges $_, find_ranges $_;
    say;
}

__DATA__
Wir alle sind auf den Witz hereingefallen.
When C++ is your hammer, every problem looks like your thumb.
<< >> 9 Einträge, 1 Seite



View all threads created 2008-12-05 09:42.