Η PDF Library for Delphi μπορεί να αντιστοιχίζει κείμενο κατά κανονική ισοδυναμία αντί κατά μονάδα κώδικα, ώστε ένα ερώτημα πληκτρολογημένο ως προσυντεθειμένος χαρακτήρας να βρίσκει περιεχόμενο αποθηκευμένο ως βασικό γράμμα συν συνδυαστικό σημάδι, και το αντίστροφο. Δύο επιλογές αναζήτησης το ελέγχουν: η soCanonicalEquivalent ενεργοποιεί την κανονικοποίηση Unicode κατά την αντιστοίχιση, και η soGraphemeClusters περιορίζει κάθε αποτέλεσμα και κάθε βήμα μπαλαντέρ σε ολόκληρες συστάδες γραφημάτων
Το σφάλμα που αυτό διορθώνει είναι ένα από τα πιο αναφερόμενα και λιγότερο κατανοητά στην αναζήτηση εγγράφων. Ένας χρήστης αναζητά ένα όνομα, δεν βλέπει αποτελέσματα, αντιγράφει το όνομα από το έγγραφο, το επικολλά στο πλαίσιο αναζήτησης, και το βρίσκει. Τίποτα δεν είναι χαλασμένο με προφανή τρόπο: οι δύο συμβολοσειρές φαίνονται πανομοιότυπες, εκτυπώνονται πανομοιότυπα, και συγκρίνονται ως άνισες, επειδή η μία είναι U+00E9 και η άλλη είναι U+0065 ακολουθούμενο από U+0301
Γιατί η ίδια λέξη συγκρίνεται ως άνιση;
Το Unicode επιτρέπει αρκετές κωδικοποιήσεις για τον ίδιο αφηρημένο χαρακτήρα. Λατινικά γράμματα με διακριτικά υπάρχουν ως προσυντεθειμένα σημεία κώδικα και ως ακολουθίες βάσης συν συνδυαστικό. Συλλαβές Χανγκούλ υπάρχουν ως προσυντεθειμένες συλλαβές και ως αποσυντεθειμένα τζάμο. Ποια από αυτές περιέχει ένα PDF εξαρτάται από τον παραγωγό, την πλατφόρμα, και μερικές φορές τη γραμματοσειρά, και τίποτα από αυτά δεν είναι ορατό στο άτομο που κάνει την αναζήτηση
Ο λόγος που η απλή αναδίπλωση πεζών-κεφαλαίων δεν λύνει αυτό είναι δομικός και όχι τυχαίος. Η αναδίπλωση πεζών-κεφαλαίων και τόνων είναι ένα προς ένα σε επίπεδο μονάδας κώδικα: η αναδιπλωμένη συμβολοσειρά έχει το ίδιο μήκος με την αρχική, οπότε μια θέση αντιστοίχισης στο αναδιπλωμένο κείμενο είναι θέση αντιστοίχισης στο αρχικό. Η κανονικοποίηση δεν είναι ένα προς ένα. Ένας προσυντεθειμένος χαρακτήρας γίνεται δύο ή τρεις μονάδες κώδικα, μια αποσυντεθειμένη ακολουθία συμπτύσσεται πίσω σε μία, και μετά από εκείνον τον μετασχηματισμό, οι θέσεις δεν ευθυγραμμίζονται πια με το κείμενο που εξαγάγατε
Διατήρηση συντεταγμένων αποτελέσματος να δείχνουν στο αρχικό κείμενο
Αυτό είναι το μέρος που καθορίζει αν η κανονικοποιημένη αναζήτηση είναι χρησιμοποιήσιμη και όχι απλώς σωστή. Κάθε μονάδα κώδικα που παράγεται από κανονικοποίηση καταγράφει την αρχική και τελική θέση του αρχικού κειμένου UTF-16 που την παρήγαγε. Αναδρομικές αποσυνθέσεις κληρονομούν το πηγαίο εύρος του γονέα τους, συνθέσεις συγχωνεύουν τα εύρη των εισόδων τους, και όταν βρεθεί μια αντιστοίχιση η βιβλιοθήκη σαρώνει το διάστημα αντιστοίχισης για τη μικρότερη αρχή και τη μεγαλύτερη λήξη
Το αποτέλεσμα είναι ότι το MatchStart, το MatchLength, οι συμβολοσειρές πλαισίου και τα δύο σημεία εισόδου αντικατάστασης όλα συνεχίζουν να απευθύνονται στο αρχικό εξαγμένο κείμενο, όχι στο κανονικοποιημένο ενδιάμεσο. Χωρίς εκείνη την αντιστοίχιση, μια κανονικοποιημένη αναζήτηση θα μπορούσε να σας πει ότι υπάρχει αποτέλεσμα αλλά όχι αξιόπιστα πού ήταν, κάτι που κάνει την επισήμανση λάθος και τη μαύρη μελάνη επικίνδυνη
Ο ίδιος ο κανονικοποιητής είναι αυτόνομος: συμπαγείς πίνακες για κανονική αποσύνθεση, σύνθεση και κανονική κλάση συνδυασμού από το Unicode 15.1, με το Χανγκούλ να χειρίζεται από τους αλγοριθμικούς κανόνες αντί από καταχωρίσεις πίνακα. Τίποτα δεν φορτώνεται από εξωτερικό αρχείο δεδομένων και καμία API κανονικοποίησης πλατφόρμας δεν καλείται, οπότε μια υπηρεσία Windows, ένας δαίμονας Linux και μια κατασκευή FPC παράγουν όλες πανομοιότυπα αποτελέσματα στην ίδια είσοδο
Αναζήτηση με κανονική ισοδυναμία
Οι επιλογές είναι ένα σύνολο, οπότε η κανονική ισοδυναμία συνδυάζεται με τις υπάρχουσες συμπεριφορές όπως η αντιστοίχιση ολόκληρης λέξης, οι μπαλαντέρ και η αναδίπλωση αδιάφορη ως προς διακριτικά:
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); // κενό εύρος σελίδων = ολόκληρο το έγγραφο
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;
Η κανονικοποίηση είναι προαιρετική ενεργοποίηση για έναν λόγο. Το χτίσιμο του κειμένου NFD και της αντιστοίχισης θέσης του κοστίζει δουλειά, και οι περισσότερες αναζητήσεις πάνω σε έγγραφα μόνο ASCII ποτέ δεν το χρειάζονται. Όταν η επιλογή χρησιμοποιείται, κάθε μπλοκ κειμένου αποθηκεύει προσωρινά δύο μετασχηματισμένες μορφές, μία με τα συνδυαστικά σημάδια αφαιρεμένα και μία χωρίς, ώστε μια παρτίδα ερωτημάτων πάνω στο ίδιο μπλοκ να κανονικοποιείται μία φορά αντί για μία φορά ανά ερώτημα. Η αναδίπλωση πεζών-κεφαλαίων συνεχίζει να ταξιδεύει τη φθηνότερη διαδρομή ένα προς ένα αμετάβλητη
Τι σπάει χωρίς όρια συστάδων γραφημάτων;
Οι μονάδες κώδικα δεν είναι χαρακτήρες, και οι χαρακτήρες δεν είναι αυτό που αντιλαμβάνονται οι χρήστες. Ένα emoji σημαίας είναι δύο σημεία κώδικα περιφερειακού δείκτη. Ένα emoji οικογένειας είναι αρκετά σημεία κώδικα ενωμένα με ενωτές μηδενικού πλάτους. Ένα ινδικό σύμπλεγμα είναι ένα σύμφωνο, ένα virama και ένα ακόμη σύμφωνο. Ένα γράμμα με δύο στοιβαγμένα διακριτικά είναι τρία σημεία κώδικα. Η αντιστοίχιση ή η κοπή στη μέση οποιουδήποτε από αυτά παράγει ένα θραύσμα που αποδίδεται ως ασυναρτησία
Η soGraphemeClusters περιορίζει και τα δύο άκρα κάθε αποτελέσματος, κυριολεκτικού ή μπαλαντέρ, σε πλήρη όρια εκτεταμένης συστάδας γραφημάτων. Η τμηματοποίηση υλοποιεί τους εκτεταμένους κανόνες: σύζευξη CR και LF, χαρακτήρες ελέγχου, κλάσεις συλλαβών Χανγκούλ, Extend και SpacingMark, Prepend, ακολουθίες emoji ZWJ, σύζευξη περιφερειακού δείκτη και σπασίματα ινδικού συμπλέγματος. Ένα όριο ποτέ δεν παράγεται μέσα σε ζεύγος αντικαταστάτη, κάτι που από μόνο του εξαλείφει μια ολόκληρη κατηγορία κατεστραμμένων αποτελεσμάτων σε οποιοδήποτε περιεχόμενο πέρα από το βασικό πολυγλωσσικό επίπεδο
Η επιλογή διέπει επίσης την κατανάλωση μπαλαντέρ, που είναι όπου μια απλοϊκή υλοποίηση θα εξακολουθούσε να κόβει λανθασμένα. Ο μπαλαντέρ μονού χαρακτήρα προχωρά ακριβώς μία πλήρη συστάδα, και η οπισθοδρόμηση για τον μπαλαντέρ ακολουθίας κινείται μόνο ανάμεσα σε όρια συστάδων:
// Χωρίς soGraphemeClusters, το "?" μπορεί να καταναλώσει μισή
// συστάδα και να επιστρέψει ένα αποτέλεσμα του οποίου το κείμενο
// τελειώνει σε κρεμασμένο συνδυαστικό σημάδι
Found := Lib.SearchText('c?té',
[soWildcards, soCanonicalEquivalent, soGraphemeClusters], '', Hits);
// Τα ίδια όρια προστατεύουν την αντικατάσταση, οπότε η μαύρη
// μελάνη και η αναγραφή περιεχομένου ποτέ δεν χωρίζουν ένα emoji
// ή ένα τονισμένο γράμμα
Replaced := Lib.SearchAndReplaceText('naïve', 'plain',
[soCanonicalEquivalent, soGraphemeClusters], '1-20');
Επιλογή επιλογών για ένα πραγματικό φορτίο εργασίας
Τρεις συνδυασμοί καλύπτουν τις περισσότερες περιπτώσεις. Για ένα εσωτερικό πλαίσιο αναζήτησης εγγράφων, η soCanonicalEquivalent συν η soDiacriticInsensitive δίνει την επιεική συμπεριφορά που περιμένουν οι χρήστες, αντιστοιχίζοντας και τις δύο μορφές κωδικοποίησης και τόσο τονισμένες όσο και άτονες ορθογραφίες. Για νομική ή κανονιστική αναζήτηση, όπου ένα ψευδώς θετικό έχει κόστος, χρησιμοποιήστε την soCanonicalEquivalent με την soCaseSensitive και την soWholeWord και αφήστε την αναδίπλωση τόνων απενεργοποιημένη, ώστε η ισοδυναμία να είναι ακριβής και ανεξάρτητη κωδικοποίησης
Για οτιδήποτε τροποποιεί το έγγραφο, προσθέστε την soGraphemeClusters χωρίς εξαίρεση. Μια αναζήτηση που επιστρέφει ελαφρώς λάθος εύρος απλώς παραπλανά έναν αναγνώστη· μια αντικατάσταση ή μαύρη μελάνη που χρησιμοποιεί το ίδιο λάθος εύρος γράφει το λάθος μέσα στο αρχείο. Οι συνέπειες του λανθασμένου εύρους αφαίρεσης καλύπτονται στην πραγματική μαύρη μελάνη και αφαίρεση περιεχομένου
Όταν η απόδοση έχει σημασία, προτιμήστε τα σημεία εισόδου παρτίδας. Η SearchTextBatch εκτελεί κάθε μη κενό ερώτημα ενώ τα μπλοκ κειμένου κάθε σελίδας είναι κάτοικοι μνήμης, κάτι που αποφεύγει την επανεξαγωγή μιας σελίδας ανά ερώτημα και επαναχρησιμοποιεί την αποθηκευμένη προσωρινά κανονικοποίηση, και οι παραλλαγές ροής εκπέμπουν αποτελέσματα χωρίς buffer μεγέθους καλούντος. Το υποκείμενο μοντέλο εξαγωγής περιγράφεται στην αναζήτηση κειμένου και απαρίθμηση στοιχείων σελίδας
Γραφές όπου αυτό δεν είναι προαιρετικό
Για τα Κορεατικά, η κανονική ισοδυναμία είναι η διαφορά ανάμεσα στο να βρεθεί ένα όνομα και στο να μη βρεθεί, επειδή προσυντεθειμένες συλλαβές και αποσυντεθειμένα τζάμο είναι και τα δύο συνηθισμένα σε πραγματικά έγγραφα. Για τα Βιετναμέζικα, στοιβαγμένα διακριτικά κάνουν τη μορφή σύνθεσης εντελώς εξαρτημένη από τον παραγωγό. Για ινδικές γραφές, ο χειρισμός συμπλεγμάτων αποφασίζει αν ένα όριο αποτελέσματος προσγειώνεται σε ευανάγνωστο σημείο. Για τα Ιαπωνικά και τα Κινέζικα, η πλευρά αναζήτησης είναι συγκριτικά απλή, αν και η πλευρά διάταξης δεν είναι, όπως περιγράφεται στην κάθετη γραφή για Ιαπωνικά και Κινέζικα
Ο εμπειρικός κανόνας είναι σύντομος: αν το σύνολο εγγράφων περιέχει οποιαδήποτε γλώσσα εκτός από Αγγλικά, ενεργοποιήστε την κανονική ισοδυναμία και μετρήστε το κόστος πριν αποφασίσετε ότι είναι πολύ ακριβό. Στα περισσότερα σύνολα εγγράφων δεν είναι, και η εναλλακτική είναι ένα χαρακτηριστικό αναζήτησης που αποτυγχάνει σιωπηλά ακριβώς στα ονόματα που ενδιαφέρει περισσότερο τους χρήστες σας να βρουν
Η αναζήτηση με επίγνωση Unicode, η εξαγωγή, η μαύρη μελάνη και η αναγραφή κειμένου μοιράζονται μία μηχανή για Delphi, C++Builder και Free Pascal· η πλήρης λίστα χαρακτηριστικών βρίσκεται στη σελίδα της PDF Library for Delphi