Zastępowanie słowa jeżeli nie jest częścią innego.

0

Cześć,
na wstępie zaznaczam, że przeszukałem forum, gotowce i google. Wydaje mi się, że problem nie jest skomplikowany i może nawet rozwiązany, jednak nie daję sobie z nim rady, ani z jego znalezieniem.
Potrzebuję dokonać StringReplace'a pod warunkiem, że część zastępowana nie jest częścią innego słowa. Wystąpień części zastępowanej może być wiele. Na przykład chcę zamienić wszystkie "kot" na "pies", ale pod warunkiem, że kot nie jest częścią innego słowa jak "kota","Szkot","aakotbbb" i tak:

"nie kot, nie sykot, ale kota bo to kot, kotowisko, kot" --> "nie pies, nie sykot, ale kota bo to pies, kotowisko, pies"

Moje starania:

tekst, zamienZ, zamienNA:String;
zamienZ:='kot';
zamienNA:='pies'
tekst:='nie kot, nie sykot, ale kota bo to kot, kotowisko, kot';
tekst:=StringReplace(tekst,' '+zamienZ+' ',' '+zamienNA+' ',[rfReplaceAll]);
tekst:=StringReplace(tekst,' '+zamienZ+',',' '+zamienNA+',',[rfReplaceAll]);
tekst:=StringReplace(tekst,' '+zamienZ+'.',' '+zamienNA+'.',[rfReplaceAll]);
tekst:=StringReplace(tekst,' '+zamienZ+';',' '+zamienNA+';',[rfReplaceAll]);

Ten sposób działa, ale ilość przypadków do sprawdzenia jest duża, a program wg. mnie robi dużo niepotrzebnej pracy. Poza tym nie podmieni wyrazu kot na początku i na końcu zmiennej tekst i to by było trzeba sprawdzać ręcznie, a nie mogę zrobić po prostu:

tekst:=StringReplace(tekst,zamienZ,zamienNA,[rfReplaceAll]);

Wystarczyłoby, żeby w StringReplace był warunek, że ma podmieniać tylko wtedy gdy przed i po miejscu podmiany nie występuje litera. Zbudowałem też coś takiego:

tekst, zamienZ, zamienNA:String;
zamienZ:='kot';
zamienNA:='pies'
tekst:='nie kot, nie sykot, ale kota bo to kot, kotowisko, kot';
gdzie:=Pos(zamienZ,tekst);
while (gdzie>0) and (not(scz(tekst[gdzie-1]))) and (not(scz(tekst[gdzie+Length(zamienZ)]))) do begin
                           tekst:=Copy(tekst,1,gdzie-1)+zamienNA+Copy(tekst,gdzie+Length(zamienZ),100000000);
                           gdzie:=Pos(zamienZ,tekst);
end;

gdzie scz jest funkcją sprawdzającą czy dany znak jest literą: a, b, c, ...:

  result:=false;
  if Pos('A',tekst)>0 then result:=true
  else if Pos('a',tekst)>0 then result:=true
  else if Pos('Ą',tekst)>0 then result:=true
 [...]

Poza tym to nie działa też idealnie, bo jednak 'ą', 'ł' itd. to nie jeden a dwa znaki i warunki w pętli while się rozrastają, a program mi się wiesza.

Piszę w ObjectPascal w Lazarusie.

0

w celu sprawdzenia czy znak jest literą czy nie , to zamiast kilkudziesięciu IF-ów , zrobił bym to tak

const
  litery = 'aąbcćefghijklłmnoópqrsśtuvwyzżźAĄBCĆDEĘFGHIJKLŁMNOÓPQRSŚTUVWXYZŻŹ';

function scz(c: char): boolean;
begin
  result := (pos(c, litery) > 0);
end;

to działa pod D2010

w Lazarusie trzeba by chyba zmienić deklarację funkcji 'scz' na poniższą:

function scz(c: string): boolean;

Słabo znam Lazarusa i niech się w tym temacie wypowiedzą mądrzejsi niż ja.
Pod Lazarusem próba skompilowania poniższego wywołania

scz('Ą')

i z argumentem funkcji zdefiniowanym jako CHAR, generuje błąd kompilatora :
unit1.pas(46,14) Error: Incompatible type for arg no. 1: Got "Constant String", expected "Char"

0

Użyj np. Lazarusowego TRegExpr: http://regexr.com/3d097

\b(kot)\b
0

Chodziło mi właśnie prawie o to co daje TRegExpr... gdyby tylko działał na polskie znaki. W przypadku przykładowego zdania z mojego tematu - działa, ale dla:
"nie śkot, nie sykot, ale kota bo to kotą, kotowisko, ćkotę" nadal widzi trzy wyrazy kot, a nie powinien żadnego.

Tak, pomysł z literami i ulepszeniem funkcji scz jest dobry, ale ciągle pozostaje problem polskich znaków. Nie kompiluje się, bo "ł", itp. zajmują dwa znaki typu char (jest to po prostu napis). Dla przykładu:
tekst:='ale łkot'
zawiera:
tekst[1]=a
tekst[2]=l
tekst[3]=e
tekst[4]=
tekst[5..6]=ł
tekst[7]=k
tekst[8]=o
tekst[9]=t

więc nawet cała moja funkcja sprawdzająca scz znaki tekst[gdzie-1] nie rozpozna tego "ł". Stosowanie zaś tekst[gdzie-2..gdzie-1] powoduje inne problemy.

0

Tulio, rozumiem, że masz kodowanie w UTF-8? Czy Delphi/Pascal (może Free Pascal) nie ma wsparcia w postaci jakiejś klasy/biblioteki do UTF-8? To by załatwiło sprawę...

Inne języki dają radę bez kombinowania, ale rozumiem, że musisz używać Pascala, bo to jakiś stary kod...?

1

W praktyce w programie pisanym pod Delphi 7 z komponentami TNT skorzystałem z tego co rozpropagowal jak dla mnie autor Total Commandera czyli modułu dostępnego z przykładami oraz dokumentacją na stronie http://regexpstudio.com/TRegExpr/TRegExpr.html - wsparcie dla UNICODE włączamy usuwając znak kropki przed jedną z dyrektyw kompilatora. Pod Delphi 7 sprawdziło się to. Również przy kodowaniu znaków ANSI.

2

@grzegorz_so głowił się wcześniej nad deklaracją funkcji scz (co to za nazwa...) dla FPC i UTF-8; Otóż wszelkie literały są kodowane w UTF-8 i nawet nie miało by to większego znaczenia, gdyby nie znaki diakrytyczne; Wszystkie polskie znaki diakrytyczne są dwubajtowe, więc np. literał Ą to nie jest znak, a łańcuch znaków, stąd niezgodność typów;

Aby móc podać dowolny znak kodowany w UTF-8, funkcja powinna przyjmować argument typu PChar, który zawiera wskazanie na pierwszy bajt znaku (dla jednobajtowych znaków jest to wskazanie na pierwszy i jedyny znak); Funkcja powinna przyjąć poniższą postać:

function FooBar(ACharacter: PChar): Boolean;

Oczywiście można użyć typu String dla argumentu - nie ma problemu;

Aby dowiedzieć się jaki konkretnie znak znajduje się pod wskaźnikiem, można użyć funkcji UTF8CharacterToUnicode, która zwróci nam w argumencie długość znaku w bajtach, a w rezultacie kod znaku; Funkcja obowiązkowa dla każdego algorytmu przetwarzającego ciągi znaków z wykorzystaniem wskaźników; W starszej wersji biblioteki standardowej, funkcja ta zadeklarowana jest w module LCLProc, w bibliotece dla FPC 3.0.0 zakończona jest dyrektywą Deprecated i redeklarowana w module LazUTF8;

Wracając do tematu - najprościej użyć wyrażeń regularnych.

0

Tulio, rozumiem, że masz kodowanie w UTF-8? Czy Delphi/Pascal (może Free Pascal) nie ma wsparcia w postaci jakiejś klasy/biblioteki do UTF-8? To by załatwiło sprawę...

Lazarus bez problemu obsługuje UTF-8 i to nie załatwia sprawy. Problem leży po stronie samego UTF-8, który polskie znaki diakrytyczne (powtarzam za @furious programming) zapisywane są dwubajtowo: https://pl.wikipedia.org/wiki/UTF-8 (patrz: wady)

Inne języki dają radę bez kombinowania, ale rozumiem, że musisz używać Pascala, bo to jakiś stary kod...?

Kod jest nowy, a pisanie w Object Pascal w Lazarusie to jeszcze nie przejaw starości :)

@olesio - sprawdzę ten sposób
@furious programming - dzięki za wyjaśnienie

Chwilowo zaprogramowałem to (i działa) w ten sposób:

  • funkcję scz (od: sprawdź czy znak [literowy]) podzieliłem na dwie mniejsze - sprawdzającą czy to znak z alfabetu angielskiego (1 bajt) o nazwie "czy_ang" i drugą sprawdzającą czy to znak typu ą,ę (2 bajty) o nazwie "czy_pol"
  • sprawdzam oddzielnie, na dwa sposoby, znaki przed "kot" i po "kot".
  • jeżeli program nigdzie nie znalazł litery to podmienia 'kot" na "pies".
podmianka1:='kot';
podmianka2:='pies';
tekst:='nie śkot, nie sykot, ale kota bo to kotą, kotowisko, ćkotę';
odkad:=1;
repeat
 gdzie:=PosEx(podmianka1,tekst,odkad);
 if (gdzie=0) then break;
 if (czy_ang(tekst[gdzie-1])) or (czy_pol(tekst[gdzie-2..gdzie-1]))
	or (czy_ang(tekst[gdzie+Length(podmianka1)])) or (czy_pol(tekst[gdzie+Length(podmianka1)..gdzie+Length(podmianka1)+1])) then begin
   odkad:=gdzie+2;
   continue;
 end;
 tekst:=Copy(tekst,1,gdzie-1)+podmianka2+Copy(tekst,gdzie+Length(podmianka1),100000000);
 odkad:=gdzie+2;
until(gdzie=0)

Nie jest to sposób zbyt efektywny, ale może komuś się przyda.

1 użytkowników online, w tym zalogowanych: 0, gości: 1