ส่องตัวอักษร

ข้อความที่คัดลอกมาจาก Excel, PDF หรือ LINE บางครั้งมีตัวที่มองไม่เห็นติดมา หน้าตาเหมือนกันทุกอย่าง แต่ค้นหาหรือเทียบแล้วไม่เจอ วางข้อความไว้ที่นี่ แล้วดูทีละตัวว่ามีอะไรซ่อนอยู่

ลองตัวอย่าง
ตัวที่ตาเห็น
48

grapheme

Code point
61

ตัวอักษรใน Unicode

.length ใน JS
61

หน่วย UTF-16

ไบต์
166

UTF-8 แบบที่ strlen() ของ PHP นับ

ปัญหาที่พบ

  • ตัวที่มองไม่เห็น1 จุด

    zero-width space, ZWNJ, ZWJ, word joiner, BOM, soft hyphen หรือตัวขึ้นบรรทัดแบบพิเศษ ตาไม่เห็นแต่โปรแกรมนับเป็นตัวอักษร ค้นหาหรือเทียบข้อความจึงไม่เจอ

  • ช่องว่างที่ไม่ใช่ space ธรรมดา1 จุด

    NBSP (U+00A0) และช่องว่างแบบอื่นหน้าตาเหมือน space แต่เป็นคนละตัว trim() ของ PHP ไม่ตัดออก และแยกคำด้วย space ก็ไม่แยกตรงนี้

  • สระเอสองตัวแทนสระแอ1 จุด

    เเ (U+0E40 สองครั้ง) หน้าตาเหมือน แ (U+0E41) แต่ค้นคำว่า แม่ จะไม่เจอ มักมาจากความเคยชินตอนพิมพ์ หรือข้อความที่ดึงมาจาก PDF

  • สระอำที่พิมพ์เป็นสองตัว1 จุด

    สระอำที่ถูกคือตัวเดียว (U+0E33) แต่บางข้อความมาเป็นนิคหิตกับสระอา (U+0E4D U+0E32) ซึ่งแสดงผลเหมือนกัน คำว่า น้ำ ในข้อความนี้จึงค้นไม่เจอ

  • วรรณยุกต์มาก่อนสระ1 จุด

    ลำดับที่ถูกคือพยัญชนะ สระบนหรือสระล่าง แล้วจึงวรรณยุกต์ ถ้าพิมพ์ไม้เอกก่อนสระอี หลายฟอนต์ยังแสดงเหมือน กี่ แต่ข้อมูลเป็นคนละชุดกัน

  • เลขไทยปนเลขอารบิก1 จุด

    ข้อความเดียวมีทั้ง ๐–๙ และ 0–9 เช่นเบอร์โทรที่พิมพ์ปนกัน validation ที่รับเฉพาะ 0–9 จะไม่ผ่าน และค้นหาหรือเรียงลำดับผิด

61 → 58 code point

ถ้าใช้แค่ NFC

หลัง NFC ยังเหลือปัญหา 6 จาก 6 จุด

NFC ไม่เปลี่ยน เเ เป็น แ ไม่รวมนิคหิตกับสระอาเป็นสระอำ ไม่ลบตัวที่มองไม่เห็น และสลับวรรณยุกต์ให้เฉพาะกับสระล่าง ส่วน NFKC กลับแยกสระอำออกเป็นสองตัว

ตรวจในเบราว์เซอร์นี้ ข้อความไม่ได้ส่งไปที่ไหน

ทีละตัวอักษร

หนึ่งช่องคือหนึ่งตัวที่ตาเห็น (grapheme) ใต้ตัวอักษรคือ code point ที่ประกอบกันอยู่

  1. เ
    • U+0E40SARA Eสระเอ
    เเ แทน แ
  2. เ
    • U+0E40SARA Eสระเอ
    เเ แทน แ
  3. จ้
    • U+0E08CHO CHANจ จาน
    • U+0E49MAI THOไม้โท
  4. ง
    • U+0E07NGO NGUง งู
  5. โ
    • U+0E42SARA Oสระโอ
  6. อ
    • U+0E2DO ANGอ อ่าง
  7. น
    • U+0E19NO NUน หนู
  8. แ
    • U+0E41SARA AEสระแอ
  9. ล้
    • U+0E25LO LINGล ลิง
    • U+0E49MAI THOไม้โท
  10. ว
    • U+0E27WO WAENว แหวน
  11. ค่
    • U+0E04KHO KHWAIค ควาย
    • U+0E48MAI EKไม้เอก
  12. ะ
    • U+0E30SARA Aสระอะ
  13. NBSP
    • U+00A0NO-BREAK SPACE
    ช่องว่างพิเศษ
  14. ย
    • U+0E22YO YAKย ยักษ์
  15. อ
    • U+0E2DO ANGอ อ่าง
  16. ด
    • U+0E14DO DEKด เด็ก
  17. ·
    • U+0020SPACE
  18. ๑
    • U+0E51DIGIT ONEเลขไทย ๑
    เลขไทย
  19. ,
    • U+002CCOMMA
  20. 2
    • U+0032DIGIT 2
  21. 5
    • U+0035DIGIT 5
  22. 0
    • U+0030DIGIT 0
  23. ·
    • U+0020SPACE
  24. บ
    • U+0E1ABO BAIMAIบ ใบไม้
  25. า
    • U+0E32SARA AAสระอา
  26. ท
    • U+0E17THO THAHANท ทหาร
  27. ZWSP
    • U+200BZERO WIDTH SPACE
    มองไม่เห็น
  28. ↵
    • U+000ALINE FEED
  29. ค่
    • U+0E04KHO KHWAIค ควาย
    • U+0E48MAI EKไม้เอก
  30. า
    • U+0E32SARA AAสระอา
  31. น้ํ
    • U+0E19NO NUน หนู
    • U+0E49MAI THOไม้โท
    • U+0E4DNIKHAHITนิคหิต
    สระอำแยก
  32. า
    • U+0E32SARA AAสระอา
    สระอำแยก
  33. เ
    • U+0E40SARA Eสระเอ
  34. ดื
    • U+0E14DO DEKด เด็ก
    • U+0E37SARA UEEสระอือ
  35. อ
    • U+0E2DO ANGอ อ่าง
  36. น
    • U+0E19NO NUน หนู
  37. นี้
    • U+0E19NO NUน หนู
    • U+0E35SARA IIสระอี
    • U+0E49MAI THOไม้โท
  38. ·
    • U+0020SPACE
  39. ส่
    • U+0E2ASO SUAส เสือ
    • U+0E48MAI EKไม้เอก
  40. ง
    • U+0E07NGO NGUง งู
  41. ไ
    • U+0E44SARA AI MAIMALAIสระไอ ไม้มลาย
  42. ด้
    • U+0E14DO DEKด เด็ก
    • U+0E49MAI THOไม้โท
  43. ก่ี
    • U+0E01KO KAIก ไก่
    • U+0E48MAI EKไม้เอก
    • U+0E35SARA IIสระอี
    วรรณยุกต์ก่อนสระ
  44. โ
    • U+0E42SARA Oสระโอ
  45. ม
    • U+0E21MO MAม ม้า
  46. ง
    • U+0E07NGO NGUง งู
  47. ค
    • U+0E04KHO KHWAIค ควาย
  48. ะ
    • U+0E30SARA Aสระอะ

ตัวที่มองไม่เห็นที่เจอบ่อย

โปรแกรมเทียบข้อความทีละ code point ไม่ได้ดูหน้าตา ตัวเหล่านี้จึงทำให้ข้อความสองชุดที่ดูเหมือนกัน ไม่เท่ากัน

Code pointชื่อมักติดมากับ
U+200BZero width spaceหน้าเว็บที่ใส่ไว้บอกจุดตัดคำภาษาไทย
U+00A0No-break spaceExcel, Word และ   ในหน้าเว็บ
U+FEFFBOMต้นไฟล์ CSV ที่บันทึกจาก Excel แบบ UTF-8
U+00ADSoft hyphenWord และ PDF ที่ตัดคำท้ายบรรทัด
U+200C, U+200DZWNJ, ZWJใช้ต่ออีโมจิ หลุดมาเดี่ยว ๆ เมื่อคัดลอกอีโมจิไม่ครบ
U+FE0FVariation selector-16ค้างอยู่หลังลบอีโมจิออกไม่หมด
U+2028Line separatorข้อความจาก PDF และแอปบางตัว

ทำความสะอาดก่อนบันทึก

ข้อความที่ผู้ใช้วางเข้ามาในฟอร์ม ควรทำความสะอาดก่อนบันทึกหรือก่อนค้นหา ฟังก์ชันนี้แก้เรื่องหลักแบบเดียวกับหน้านี้ ใน Laravel เรียกได้จาก prepareForValidation() ของ FormRequest

function cleanThai(string $text): string
{
    // ตัวที่มองไม่เห็น (เว้น ZWJ ที่ใช้ต่ออีโมจิ) และช่องว่างพิเศษ
    $invisible = '/[\x{200B}\x{200C}\x{2060}\x{FEFF}\x{00AD}]/u';
    $spaces = '/[\x{00A0}\x{2000}-\x{200A}\x{202F}\x{3000}]/u';
    $text = preg_replace([$invisible, $spaces], ['', ' '], $text);

    // เเ เป็น แ และนิคหิตกับสระอาเป็นสระอำ
    $text = strtr($text, [
        "\u{0E40}\u{0E40}" => "\u{0E41}",
        "\u{0E4D}\u{0E32}" => "\u{0E33}",
    ]);

    // วรรณยุกต์ที่มาก่อนสระ สลับให้สระมาก่อน
    $tone = '[\x{0E48}-\x{0E4B}]';
    $vowel = '[\x{0E31}\x{0E34}-\x{0E39}]';

    return preg_replace("/($tone)($vowel)/u", '$2$1', $text);
}

Normalizer::normalize() แบบ NFC ไม่ช่วยเรื่อง เเ และสระอำ ส่วน trim() ไม่ตัด NBSP และ zero width space ที่หัวท้ายข้อความ

เครื่องมืออื่น

ดูทั้งหมด

มีงานอยากให้ช่วย เล่าให้เราฟัง

บอกสั้น ๆ ว่าอยากทำอะไร หรือระบบเดิมติดตรงไหน เดี๋ยวเราติดต่อกลับทางที่คุณสะดวก

คุยโปรเจกต์

หรืออีเมลถึงเราโดยตรง [email protected]