PDF Library for Delphi lahko ujema besedilo po kanonični enakovrednosti in ne po kodni enoti, tako da poizvedba, vtipkana kot vnaprej sestavljen znak, najde vsebino, shranjeno kot osnovna črka plus kombinirajoči znak, in obratno. To nadzorujeta dve možnosti iskanja: soCanonicalEquivalent med ujemanjem omogoči normalizacijo Unicode, soGraphemeClusters pa vsak zadetek in vsak korak nadomestnega znaka omeji na cele grafemske gruče
Napaka, ki jo to popravi, je ena najpogosteje javljenih in najmanj razumljenih pri iskanju po dokumentih. Uporabnik išče ime, ne vidi rezultatov, ime prekopira iz dokumenta, ga prilepi v iskalno polje in ga najde. Nič ni na očiten način pokvarjeno: niza sta videti enaka, natisneta se enako, primerjava pa pokaže neenakost, ker je eden U+00E9, drugi pa U+0065, ki mu sledi U+0301
Zakaj se ista beseda primerja kot neenaka?
Unicode dovoljuje več kodiranj za isti abstraktni znak. Latinične črke z diakritičnimi znamenji obstajajo kot vnaprej sestavljene kodne točke in kot zaporedja osnova plus kombinirajoči znak. Zlogi hangula obstajajo kot vnaprej sestavljeni zlogi in kot razstavljeni jamo. Katerega PDF vsebuje, je odvisno od izdelovalca, platforme in včasih pisave, nič od tega pa ni vidno osebi, ki izvaja iskanje
Razlog, da preprosto zvijanje velikosti črk tega ne reši, je strukturen in ne naključen. Zvijanje velikosti črk in zvijanje naglasov sta ena na ena na ravni kodne enote: zviti niz ima enako dolžino kot izvirnik, zato je položaj ujemanja v zvitem besedilu tudi položaj ujemanja v izvirniku. Normalizacija ni ena na ena. En vnaprej sestavljen znak postane dve ali tri kodne enote, razstavljeno zaporedje se sesuje nazaj v eno, po tej transformaciji pa se položaji ne ujemajo več z besedilom, ki ste ga izvlekli
Ohranjanje koordinat zadetka usmerjenih na izvirno besedilo
To je del, ki odloča, ali je normalizirano iskanje uporabno in ne le pravilno. Vsaka kodna enota, ki jo ustvari normalizacija, zabeleži začetni in končni položaj izvirnega besedila UTF-16, ki jo je ustvarilo. Rekurzivna razstavljanja podedujejo izvorni razpon svojega starša, sestavljanja združijo razpone svojih vhodov, ko pa je najden zadetek, knjižnica preišče interval preslikave za najmanjši začetek in največji konec
Učinek je, da MatchStart, MatchLength, kontekstna niza in obe vstopni točki za zamenjavo vsi še naprej naslavljajo izvirno izvlečeno besedilo in ne normaliziranega vmesnega. Brez te preslikave bi vam normalizirano iskanje lahko povedalo, da zadetek obstaja, ne pa zanesljivo, kje je bil, kar poudarjanje naredi napačno, redakcijo pa nevarno
Sam normalizator je samostojen: kompaktne tabele za kanonično razstavljanje, sestavljanje in kanonični razred kombiniranja iz Unicode 15.1, hangul pa je obravnavan z algoritemskimi pravili in ne z vnosi v tabeli. Nič ni naloženo iz zunanje podatkovne datoteke in noben API za normalizacijo platforme ni poklican, tako da storitev Windows, demon Linux in izgradnja FPC pri istem vhodu vsi dajo identične rezultate
Iskanje s kanonično enakovrednostjo
Možnosti so množica, zato se kanonična enakovrednost kombinira z obstoječimi vedenji, kot so ujemanje celih besed, nadomestni znaki in zvijanje, neobčutljivo na diakritična znamenja:
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); // prazen razpon strani = cel 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 izbirna z razlogom. Gradnja besedila NFD in njegove preslikave položajev stane delo, večina iskanj po samo-ASCII dokumentih pa je nikoli ne potrebuje. Kadar je možnost uporabljena, vsak besedilni blok predpomni dve preoblikovani obliki, eno z odstranjenimi kombinirajočimi znamenji in eno brez, tako da paket poizvedb nad istim blokom normalizira enkrat namesto enkrat na poizvedbo. Zvijanje velikosti črk še naprej nespremenjeno potuje po cenejši poti ena na ena
Kaj se pokvari brez mej grafemskih gruč?
Kodne enote niso znaki, znaki pa niso to, kar zaznavajo uporabniki. Emotikon zastave sta dve kodni točki regionalnega indikatorja. Emotikon družine je več kodnih točk, povezanih z združevalniki ničelne širine. Indijski ligaturni sklop je soglasnik, virama in drug soglasnik. Črka z dvema naloženima naglasoma so tri kodne točke. Ujemanje ali rezanje sredi katerega koli od teh ustvari fragment, ki se upodobi kot smet
soGraphemeClusters omeji oba konca vsakega zadetka, dobesednega ali z nadomestnim znakom, na popolne meje razširjenih grafemskih gruč. Segmentacija implementira razširjena pravila: parjenje CR in LF, kontrolne znake, razrede zlogov hangula, Extend in SpacingMark, Prepend, zaporedja emotikonov ZWJ, parjenje regionalnih indikatorjev in prelome indijskih ligaturnih sklopov. Meja se nikoli ne ustvari znotraj nadomestnega para, kar samo po sebi izloči celo skupino pokvarjenih rezultatov pri vsaki vsebini onkraj osnovne večjezične ravnine
Možnost ureja tudi porabo nadomestnih znakov, kjer bi naivna implementacija še vedno napačno rezala. Enoznakovni nadomestni znak napreduje natanko za eno popolno gručo, vračanje za nadomestni znak zaporedja pa se premika samo med mejami gruč:
// Brez soGraphemeClusters lahko "?" porabi polovico gruče in
// vrne zadetek, katerega besedilo se konča z visečim kombinirajočim znamenjem
Found := Lib.SearchText('c?té',
[soWildcards, soCanonicalEquivalent, soGraphemeClusters], '', Hits);
// Iste meje ščitijo tudi zamenjavo, tako da redakcija in
// prepisovanje vsebine nikoli ne razcepita emotikona ali naglašene črke
Replaced := Lib.SearchAndReplaceText('naïve', 'plain',
[soCanonicalEquivalent, soGraphemeClusters], '1-20');
Izbira možnosti za pravo delovno obremenitev
Tri kombinacije pokrivajo večino primerov. Za notranje iskalno polje dokumentov soCanonicalEquivalent plus soDiacriticInsensitive dasta prizanesljivo vedenje, ki ga uporabniki pričakujejo, ujemata pa obe kodni obliki ter naglašen in nenaglašen zapis. Za pravno ali skladnostno iskanje, kjer ima lažni pozitiv svojo ceno, uporabite soCanonicalEquivalent z soCaseSensitive in soWholeWord ter pustite zvijanje naglasov izklopljeno, tako da je enakovrednost natančna in neodvisna od kodiranja
Za vse, kar spreminja dokument, brez izjeme dodajte soGraphemeClusters. Iskanje, ki vrne rahlo napačen razpon, samo zavede bralca; zamenjava ali redakcija, ki uporabi isti napačen razpon, napako zapiše v datoteko. Posledice napačnih razponov odstranitve so opisane v pravi redakciji in odstranjevanju vsebine
Kadar je pomembna prepustnost, dajte prednost paketnim vstopnim točkam. SearchTextBatch izvede vsako neprazno poizvedbo, medtem ko so besedilni bloki vsake strani rezidenčni, kar se izogne ponovnemu izvlečenju strani na poizvedbo in ponovno uporabi predpomnjeno normalizacijo, pretočne različice pa izpišejo zadetke brez medpomnilnika velikosti klicatelja. Spodaj ležeči model izvlečenja je opisan v iskanju besedila in naštevanju elementov strani
Pisave, kjer to ni izbirno
Za korejščino je kanonična enakovrednost razlika med tem, da ime najdete, in tem, da ga ne najdete, ker so v pravih dokumentih pogosti tako vnaprej sestavljeni zlogi kot razstavljeni jamo. Za vietnamščino naložena diakritična znamenja obliko sestave naredijo v celoti odvisno od izdelovalca. Za indijske pisave obravnava ligaturnih sklopov odloča, ali meja zadetka pristane na berljivem mestu. Za japonščino in kitajščino je stran iskanja razmeroma preprosta, čeprav stran postavitve ni, kot je opisano v navpičnem pisanju za japonščino in kitajščino
Praktično pravilo je kratko: če korpus vsebuje kateri koli jezik razen angleščine, vklopite kanonično enakovrednost in izmerite ceno, preden odločite, da je predraga. Pri večini naborov dokumentov ni, alternativa pa je funkcija iskanja, ki tiho odpove prav pri imenih, za katera vašim uporabnikom najbolj ni vseeno, ali jih najdejo
Iskanje, ozaveščeno o Unicode, izvlečenje, redakcija in prepisovanje besedila si delijo en mehanizem za Delphi, C++Builder in Free Pascal; celoten seznam funkcij je na strani PDF Library for Delphi