PDF Library for Delphi สามารถจับคู่ข้อความตามความเทียบเท่ากันเชิงมาตรฐานแทนที่จะจับคู่ตามหน่วยรหัส ดังนั้นคำค้นที่พิมพ์เป็นอักขระประกอบล่วงหน้าจะพบเนื้อหาที่เก็บเป็นตัวอักษรฐานบวกเครื่องหมายประกอบ และในทางกลับกันด้วย ตัวเลือกการค้นหาสองตัวควบคุมเรื่องนี้ คือ soCanonicalEquivalent เปิดใช้งานการทำให้เป็นบรรทัดฐานยูนิโค้ดระหว่างการจับคู่ และ soGraphemeClusters จำกัดทุกผลลัพธ์และทุกขั้นตอนไวลด์การ์ดให้อยู่บนกลุ่มอักขระที่มองเห็นแบบสมบูรณ์เท่านั้น
บั๊กที่สิ่งนี้แก้ไขคือหนึ่งในบั๊กที่ถูกรายงานมากที่สุดและถูกเข้าใจน้อยที่สุดในการค้นหาเอกสาร ผู้ใช้ค้นหาชื่อหนึ่ง ไม่พบผลลัพธ์ใดเลย คัดลอกชื่อออกจากเอกสาร วางลงในกล่องค้นหา แล้วก็พบมัน ไม่มีอะไรพังในแบบที่เห็นได้ชัด สองสตริงดูเหมือนกันทุกประการ พิมพ์ออกมาเหมือนกันทุกประการ แต่เปรียบเทียบแล้วไม่เท่ากัน เพราะตัวหนึ่งคือ U+00E9 และอีกตัวคือ U+0065 ตามด้วย U+0301
เหตุใดคำเดียวกันจึงเปรียบเทียบแล้วไม่เท่ากัน
ยูนิโค้ดอนุญาตให้มีการเข้ารหัสหลายแบบสำหรับอักขระเชิงนามธรรมเดียวกัน ตัวอักษรละตินที่มีเครื่องหมายเสริมมีอยู่ทั้งในรูปจุดรหัสประกอบล่วงหน้าและในรูปฐานบวกลำดับการประกอบ พยางค์ฮันกึลมีอยู่ทั้งในรูปพยางค์ประกอบล่วงหน้าและในรูปจามอที่แยกส่วนแล้ว PDF จะมีแบบใดขึ้นอยู่กับตัวสร้าง แพลตฟอร์ม และบางครั้งก็ฟอนต์ และไม่มีสิ่งใดในนั้นมองเห็นได้สำหรับผู้ที่กำลังค้นหาเลย
เหตุผลที่การพับตัวพิมพ์ธรรมดาแก้ปัญหานี้ไม่ได้เป็นเรื่องเชิงโครงสร้าง ไม่ใช่เรื่องบังเอิญ การพับตัวพิมพ์และการพับเครื่องหมายเสริมเป็นแบบหนึ่งต่อหนึ่งในระดับหน่วยรหัส สตริงที่พับแล้วมีความยาวเท่ากับต้นฉบับ ดังนั้นตำแหน่งที่จับคู่ได้ในข้อความที่พับแล้วก็คือตำแหน่งที่จับคู่ได้ในต้นฉบับ การทำให้เป็นบรรทัดฐานไม่ใช่แบบหนึ่งต่อหนึ่ง อักขระประกอบล่วงหน้าหนึ่งตัวกลายเป็นสองหรือสามหน่วยรหัส ลำดับที่แยกส่วนแล้วยุบกลับเป็นหนึ่งเดียว และหลังจากการแปลงนั้น ตำแหน่งจะไม่ตรงกับข้อความที่คุณสกัดออกมาอีกต่อไป
การรักษาพิกัดผลลัพธ์การค้นหาให้ชี้ไปที่ข้อความต้นฉบับ
นี่คือส่วนที่ตัดสินว่าการค้นหาแบบทำให้เป็นบรรทัดฐานแล้วใช้งานได้จริงหรือแค่ถูกต้องเท่านั้น หน่วยรหัสทุกตัวที่สร้างขึ้นจากการทำให้เป็นบรรทัดฐานจะบันทึกตำแหน่งเริ่มต้นและสิ้นสุดของข้อความ UTF-16 ต้นฉบับที่สร้างมันขึ้นมา การแยกส่วนแบบเรียกซ้ำสืบทอดช่วงต้นทางของพาเรนต์ของมัน การประกอบรวมช่วงของข้อมูลนำเข้าของมันเข้าด้วยกัน และเมื่อพบผลลัพธ์การจับคู่ ไลบรารีจะสแกนช่วงการแม็ปเพื่อหาจุดเริ่มต้นที่เล็กที่สุดและจุดสิ้นสุดที่ใหญ่ที่สุด
ผลลัพธ์คือ MatchStart, MatchLength สตริงบริบท และจุดเข้าถึงการแทนที่ทั้งสองจุด ล้วนยังคงอ้างอิงถึงข้อความที่สกัดออกมาต้นฉบับ ไม่ใช่ตัวกลางที่ทำให้เป็นบรรทัดฐานแล้ว หากไม่มีการแม็ปนั้น การค้นหาแบบทำให้เป็นบรรทัดฐานอาจบอกคุณได้ว่ามีผลลัพธ์อยู่ แต่บอกตำแหน่งที่แท้จริงไม่ได้อย่างน่าเชื่อถือ ซึ่งทำให้การไฮไลต์ผิดพลาดและการปิดบังข้อมูลเป็นอันตราย
ตัวทำให้เป็นบรรทัดฐานเองพึ่งพาตัวเองอย่างสมบูรณ์ คือมีตารางแบบกะทัดรัดสำหรับการแยกส่วนเชิงมาตรฐาน การประกอบ และคลาสการรวมเชิงมาตรฐานจากยูนิโค้ด 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 ล้วนไม่เคยต้องการมันเลย เมื่อใช้ตัวเลือกนี้ แต่ละบล็อกข้อความจะแคชรูปแบบที่แปลงแล้วสองรูปแบบ คือแบบที่ลบเครื่องหมายประกอบออกและแบบที่ไม่ลบ ดังนั้นชุดคำค้นหาบนบล็อกเดียวกันจึงทำให้เป็นบรรทัดฐานเพียงครั้งเดียวแทนที่จะทำครั้งละคำค้น การพับตัวพิมพ์ยังคงเดินทางบนเส้นทางแบบหนึ่งต่อหนึ่งที่ถูกกว่าโดยไม่เปลี่ยนแปลง
อะไรจะพังหากไม่มีขอบเขตกลุ่มอักขระที่มองเห็น
หน่วยรหัสไม่ใช่อักขระ และอักขระก็ไม่ใช่สิ่งที่ผู้ใช้รับรู้ อิโมจิธงชาติคือจุดรหัสตัวบ่งชี้ภูมิภาคสองจุด อิโมจิครอบครัวคือจุดรหัสหลายจุดที่เชื่อมกันด้วยตัวเชื่อมความกว้างศูนย์ พยัญชนะประสมแบบอินดิกคือพยัญชนะ วิรามะ และพยัญชนะอีกตัวหนึ่ง ตัวอักษรที่มีเครื่องหมายเสริมซ้อนกันสองตัวคือสามจุดรหัส การจับคู่หรือตัดกลางสิ่งเหล่านี้จะสร้างชิ้นส่วนที่แสดงผลเป็นขยะ
soGraphemeClusters จำกัดปลายทั้งสองด้านของทุกผลลัพธ์ ไม่ว่าจะเป็นตัวอักษรตรงหรือไวลด์การ์ด ให้อยู่บนขอบเขตกลุ่มอักขระที่มองเห็นแบบขยายที่สมบูรณ์ การแบ่งส่วนนำกฎแบบขยายไปใช้ คือการจับคู่ CR และ LF อักขระควบคุม คลาสพยางค์ฮันกึล Extend และ SpacingMark, Prepend ลำดับอิโมจิแบบ ZWJ การจับคู่ตัวบ่งชี้ภูมิภาค และการแบ่งพยัญชนะประสมแบบอินดิก ขอบเขตจะไม่มีวันถูกสร้างขึ้นภายในคู่ surrogate เลย ซึ่งเพียงข้อเดียวนี้ก็กำจัดผลลัพธ์ที่เสียหายทั้งกลุ่มบนเนื้อหาใดๆ ที่อยู่นอกเหนือระนาบพหุภาษาพื้นฐานได้
ตัวเลือกนี้ยังควบคุมการใช้ไวลด์การ์ดด้วย ซึ่งเป็นจุดที่การนำไปใช้งานแบบไร้เดียงสายังคงตัดผิดพลาดอยู่ ไวลด์การ์ดตัวอักษรเดี่ยวเลื่อนไปข้างหน้าทีละหนึ่งกลุ่มที่สมบูรณ์พอดี และการย้อนกลับสำหรับไวลด์การ์ดแบบต่อเนื่องเคลื่อนที่เฉพาะระหว่างขอบเขตกลุ่มเท่านั้น:
// หากไม่มี soGraphemeClusters "?" อาจกินครึ่งกลุ่ม และ
// คืนผลลัพธ์ที่ข้อความลงท้ายด้วยเครื่องหมายประกอบที่ค้างอยู่
Found := Lib.SearchText('c?té',
[soWildcards, soCanonicalEquivalent, soGraphemeClusters], '', Hits);
// ขอบเขตเดียวกันนี้ปกป้องการแทนที่ ดังนั้นการปิดบังข้อมูลและ
// การเขียนเนื้อหาใหม่จะไม่มีวันแบ่งอิโมจิหรือตัวอักษรที่มีเครื่องหมายเสริมได้
Replaced := Lib.SearchAndReplaceText('naïve', 'plain',
[soCanonicalEquivalent, soGraphemeClusters], '1-20');
การเลือกตัวเลือกสำหรับงานจริง
การผสมผสานสามแบบครอบคลุมกรณีส่วนใหญ่ สำหรับกล่องค้นหาเอกสารภายใน soCanonicalEquivalent บวกกับ soDiacriticInsensitive จะให้พฤติกรรมที่ยืดหยุ่นตามที่ผู้ใช้คาดหวัง จับคู่ได้ทั้งรูปแบบการเข้ารหัสและการสะกดทั้งแบบมีและไม่มีเครื่องหมายเสริม สำหรับการค้นหาทางกฎหมายหรือการปฏิบัติตามข้อกำหนด ซึ่งผลบวกลวงมีต้นทุน ให้ใช้ soCanonicalEquivalent ร่วมกับ soCaseSensitive และ soWholeWord และปิดการพับเครื่องหมายเสริมไว้ เพื่อให้ความเทียบเท่ากันแม่นยำและไม่ขึ้นกับการเข้ารหัส
สำหรับสิ่งใดก็ตามที่แก้ไขเอกสาร ให้เพิ่ม soGraphemeClusters โดยไม่มีข้อยกเว้น การค้นหาที่คืนช่วงผิดเล็กน้อยเพียงแค่ทำให้ผู้อ่านเข้าใจผิดเท่านั้น แต่การแทนที่หรือการปิดบังข้อมูลที่ใช้ช่วงผิดเดียวกันนั้นจะเขียนความผิดพลาดลงในไฟล์เลย ผลที่ตามมาของการกำหนดช่วงลบผิดพลาดอธิบายไว้ในการปิดบังข้อมูลจริงและการลบเนื้อหา
เมื่อปริมาณงานสำคัญ ให้เลือกใช้จุดเข้าถึงแบบแบตช์ SearchTextBatch รันทุกคำค้นที่ไม่ว่างเปล่าในขณะที่บล็อกข้อความของแต่ละหน้ายังคงอยู่ในหน่วยความจำ ซึ่งหลีกเลี่ยงการสกัดหน้าซ้ำต่อหนึ่งคำค้น และใช้การทำให้เป็นบรรทัดฐานที่แคชไว้ซ้ำ และรูปแบบสตรีมมิงจะปล่อยผลลัพธ์ออกมาโดยไม่ต้องใช้บัฟเฟอร์ที่ผู้เรียกกำหนดขนาด แบบจำลองการสกัดที่อยู่เบื้องหลังอธิบายไว้ในการค้นหาข้อความและการแจกแจงอิลิเมนต์หน้า
สคริปต์ที่เรื่องนี้ไม่ใช่ตัวเลือก
สำหรับภาษาเกาหลี ความเทียบเท่ากันเชิงมาตรฐานคือความแตกต่างระหว่างการพบชื่อกับการไม่พบ เพราะพยางค์ประกอบล่วงหน้าและจามอที่แยกส่วนแล้วล้วนพบได้ทั่วไปในเอกสารจริง สำหรับภาษาเวียดนาม เครื่องหมายเสริมที่ซ้อนกันทำให้รูปแบบการประกอบขึ้นอยู่กับตัวสร้างโดยสิ้นเชิง สำหรับสคริปต์อินดิก การจัดการพยัญชนะประสมตัดสินว่าขอบเขตของผลลัพธ์จะตกในตำแหน่งที่อ่านได้หรือไม่ สำหรับภาษาญี่ปุ่นและจีน ฝั่งการค้นหาค่อนข้างง่ายเมื่อเทียบกัน แม้ว่าฝั่งเลย์เอาต์จะไม่ง่ายก็ตาม ตามที่อธิบายไว้ในการเขียนแนวตั้งสำหรับภาษาญี่ปุ่นและจีน
กฎง่ายๆ ที่จำง่ายคือ หากชุดข้อมูลมีภาษาใดๆ นอกจากภาษาอังกฤษ ให้เปิดความเทียบเท่ากันเชิงมาตรฐานไว้และวัดต้นทุนก่อนที่จะตัดสินว่ามันแพงเกินไป ในชุดเอกสารส่วนใหญ่แล้วมันไม่แพง และทางเลือกอื่นคือคุณสมบัติการค้นหาที่ล้มเหลวอย่างเงียบๆ กับชื่อที่ผู้ใช้ของคุณใส่ใจที่จะค้นหามากที่สุดพอดี
การค้นหาที่รู้จักยูนิโค้ด การสกัด การปิดบังข้อมูล และการเขียนข้อความใหม่ ล้วนใช้เอนจินเดียวกันสำหรับ Delphi, C++Builder และ Free Pascal รายการคุณสมบัติทั้งหมดอยู่ที่หน้า PDF Library for Delphi