PDF Library for Delphi može da poklapa tekst po kanonskoj ekvivalenciji umesto po jedinici koda, tako da upit ukucan kao prekomponovan karakter pronalazi sadržaj sačuvan kao osnovno slovo plus kombinujući znak, i obrnuto. Dve opcije pretrage to kontrolišu: soCanonicalEquivalent uključuje Unikod normalizaciju tokom poklapanja, a soGraphemeClusters ograničava svaki pogodak i svaki džoker korak na cele grafemske klastere
Greška koju ovo rešava je jedna od najprijavljivanijih i najmanje shvaćenih u pretrazi dokumenata. Korisnik pretraži ime, ne vidi rezultate, kopira ime iz dokumenta, nalepi ga u polje za pretragu, i pronađe ga. Ništa nije očigledno pokvareno: dva stringa izgledaju identično, štampaju se identično, a upoređuju se kao nejednaki, jer je jedan U+00E9, a drugi U+0065 praćen sa U+0301
Zašto se ista reč upoređuje kao nejednaka?
Unikod dozvoljava nekoliko kodiranja za isti apstraktan karakter. Latinična slova sa dijakritičkim znacima postoje kao prekomponovane kodne tačke i kao nizovi osnova plus kombinujući znak. Hangul slogovi postoje kao prekomponovani slogovi i kao dekomponovani jamo. Koje od toga PDF sadrži zavisi od proizvođača, platforme, a ponekad i fonta, a ništa od toga nije vidljivo osobi koja vrši pretragu
Razlog zašto jednostavno sklapanje velikih i malih slova ovo ne rešava je strukturan, a ne slučajan. Sklapanje velikih i malih slova i sklapanje akcenata su jedan-na-jedan na nivou jedinice koda: sklopljen string ima istu dužinu kao original, tako da je pozicija pogotka u sklopljenom tekstu pozicija pogotka u originalu. Normalizacija nije jedan-na-jedan. Jedan prekomponovan karakter postaje dve ili tri jedinice koda, dekomponovan niz se skuplja nazad u jedan, a nakon te transformacije, pozicije se više ne poklapaju sa tekstom koji ste izvukli
Održavanje koordinata pogotka usmerenih na originalan tekst
Ovo je deo koji određuje da li je normalizovana pretraga upotrebljiva, a ne samo ispravna. Svaka jedinica koda proizvedena normalizacijom beleži početnu i krajnju poziciju originalnog UTF-16 teksta koji ju je proizveo. Rekurzivne dekompozicije nasleđuju izvorni opseg svog roditelja, kompozicije spajaju opsege svojih ulaza, a kada se pronađe pogodak, biblioteka skenira interval mapiranja za najmanji početak i najveći kraj
Efekat je da MatchStart, MatchLength, kontekstni stringovi i obe ulazne tačke zamene i dalje adresiraju originalan izvučen tekst, ne normalizovan posrednik. Bez tog mapiranja, normalizovana pretraga bi mogla reći da pogodak postoji, ali ne i pouzdano gde je bio, što isticanje čini pogrešnim, a redakciju opasnom
Sam normalizator je samodovoljan: kompaktne tabele za kanonsku dekompoziciju, kompoziciju i kanonsku klasu kombinovanja iz Unikoda 15.1, sa Hangul-om obrađenim algoritamskim pravilima, a ne tabelnim unosima. Ništa se ne učitava iz eksternog fajla podataka, a nijedan platformski API normalizacije se ne poziva, tako da Windows servis, Linux demon i FPC izgradnja svi proizvode identične rezultate na istom ulazu
Pretraga sa kanonskom ekvivalencijom
Opcije su skup, tako da se kanonska ekvivalencija kombinuje sa postojećim ponašanjima poput poklapanja cele reči, džokera i sklapanja neosetljivog na dijakritičke znake:
uses
PDFlibrary;
var
Lib: TPDFlib;
Hits: array of TPDFlibSearchHit;
Found, I: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('contracts.pdf', '');
SetLength(Hits, 500);
Found := Lib.SearchText('Bäcker', [soCanonicalEquivalent, soWholeWord],
'', Hits); // prazan opseg stranica = ceo dokument
for I := 0 to Found - 1 do
Log(Format('page %d: "%s" at %d (%d chars)',
[Hits[I].Page, Hits[I].MatchText, Hits[I].MatchStart,
Hits[I].MatchLength]));
finally
Lib.Free;
end;
end;
Normalizacija je opciona iz razloga. Izgradnja NFD teksta i njegovog mapiranja pozicija košta rad, a većina pretraga preko dokumenata koji su isključivo ASCII to nikada ne treba. Kada se opcija koristi, svaki tekstualni blok keširano čuva dva transformisana oblika, jedan sa uklonjenim kombinujućim znacima i jedan bez toga, tako da serija upita preko istog bloka normalizuje jednom umesto po upitu. Sklapanje velikih i malih slova nastavlja jeftinijom jedan-na-jedan putanjom nepromenjeno
Šta se pokvari bez granica grafemskih klastera?
Jedinice koda nisu karakteri, a karakteri nisu ono što korisnici percipiraju. Emoji zastave je dve regionalne indikatorske kodne tačke. Emoji porodice je nekoliko kodnih tačaka spojenih spojnicama nulte širine. Indijski konjukt je saglasnik, virama i drugi saglasnik. Slovo sa dva naslagana akcenta je tri kodne tačke. Poklapanje ili sečenje usred bilo čega od ovoga proizvodi fragment koji se renderuje kao smeće
soGraphemeClusters ograničava oba kraja svakog pogotka, doslovnog ili džoker, na kompletne granice proširenog grafemskog klastera. Segmentacija implementira proširena pravila: uparivanje CR i LF, kontrolne karaktere, klase Hangul slogova, Extend i SpacingMark, Prepend, emoji ZWJ nizove, uparivanje regionalnih indikatora i prekide indijskih konjukta. Granica se nikada ne proizvodi usred surogatnog para, što samo po sebi eliminiše celu klasu oštećenih rezultata na bilo kom sadržaju van osnovne višejezične ravni
Opcija takođe upravlja potrošnjom džokera, gde bi naivna implementacija i dalje sekla pogrešno. Džoker za jedan karakter napreduje tačno za jedan kompletan klaster, a povratak za džoker niza pomera se samo između granica klastera:
// Bez soGraphemeClusters, "?" može potrošiti pola klastera i
// vratiti pogodak čiji tekst završava visećim kombinujućim znakom
Found := Lib.SearchText('c?té',
[soWildcards, soCanonicalEquivalent, soGraphemeClusters], '', Hits);
// Iste granice štite zamenu, tako da redakcija i prepisivanje
// sadržaja nikada ne podele emoji ili slovo sa akcentom
Replaced := Lib.SearchAndReplaceText('naïve', 'plain',
[soCanonicalEquivalent, soGraphemeClusters], '1-20');
Biranje opcija za stvaran posao
Tri kombinacije pokrivaju većinu slučajeva. Za interno polje pretrage dokumenata, soCanonicalEquivalent plus soDiacriticInsensitive daje popustljivo ponašanje koje korisnici očekuju, poklapajući oba oblika kodiranja i akcentovano i neakcentovano pisanje. Za pravnu ili usklađenu pretragu, gde lažno pozitivan rezultat ima cenu, koristite soCanonicalEquivalent sa soCaseSensitive i soWholeWord i ostavite sklapanje akcenata isključeno, tako da je ekvivalencija tačna i nezavisna od kodiranja
Za sve što menja dokument, dodajte soGraphemeClusters bez izuzetka. Pretraga koja vrati malo pogrešan opseg samo zavede čitaoca; zamena ili redakcija koja koristi isti pogrešan opseg upisuje grešku u fajl. Posledice pogrešnih opsega uklanjanja su obrađene u pravoj redakciji i uklanjanju sadržaja
Kada je propusnost bitna, dajte prednost paketnim ulaznim tačkama. SearchTextBatch pokreće svaki neprazan upit dok su tekstualni blokovi svake stranice rezidentni, što izbegava ponovno izvlačenje stranice po upitu i ponovo koristi keširanu normalizaciju, a varijante za striming emituju pogotke bez bafera veličine pozivaoca. Model izvlačenja ispod je opisan u pretrazi teksta i nabrajanju elemenata stranice
Pisma gde ovo nije opciono
Za korejski, kanonska ekvivalencija je razlika između pronalaženja imena i njegovog nepronalaženja, jer su prekomponovani slogovi i dekomponovan jamo oba česti u stvarnim dokumentima. Za vijetnamski, naslagani dijakritički znaci čine oblik kompozicije potpuno zavisnim od proizvođača. Za indijska pisma, rukovanje konjuktima odlučuje da li granica pogotka pada na čitljivo mesto. Za japanski i kineski, strana pretrage je relativno jednostavna, iako strana rasporeda nije, kao što je opisano u vertikalnom pisanju za japanski i kineski
Pravilo je kratko: ako korpus sadrži bilo koji jezik osim engleskog, uključite kanonsku ekvivalenciju i izmerite cenu pre nego što odlučite da je preskupa. U većini skupova dokumenata nije, a alternativa je funkcija pretrage koja tiho ne uspeva upravo na imenima do kojih je vašim korisnicima najviše stalo
Unikod-svesna pretraga, izvlačenje, redakcija i prepisivanje teksta dele jedan pogon za Delphi, C++Builder i Free Pascal; kompletna lista funkcija je na stranici PDF Library for Delphi