NFC và NFD là hai dạng chuẩn hoá Unicode: NFC (dựng sẵn) gộp chữ và dấu thành một ký tự khi có thể, ví dụ "ệ" là một ký tự U+1EC7; NFD (tách rời) bẻ chữ thành chữ gốc cộng các dấu kết hợp, "ệ" thành ba ký tự e + dấu nặng + dấu mũ. Hai dạng hiện ra giống hệt nhau trên màn hình nhưng là hai chuỗi khác nhau trong máy, nên tìm kiếm, so trùng, sắp xếp, đếm độ dài có thể sai. Cách xử lý chuẩn: đưa mọi văn bản tiếng Việt về NFC trước khi lưu và so sánh.
Một chữ, nhiều cách lưu
Unicode chứa cả chữ dựng sẵn (như ệ, ở, ữ) lẫn các dấu kết hợp (dấu huyền U+0300, sắc U+0301, ngã U+0303, hỏi U+0309, nặng U+0323, mũ U+0302, trăng U+0306, móc U+031B). Vì thế cùng một chữ Việt có thể được ghi theo nhiều cách:
| Cách ghi chữ "ệ" | Chuỗi mã | Số ký tự |
|---|---|---|
| Dựng sẵn (NFC) | U+1EC7 | 1 |
| Tách rời hoàn toàn (NFD) | U+0065 U+0323 U+0302 | 3 |
| Nửa dựng nửa tách (kiểu Windows-1258, một số bộ gõ "tổ hợp") | U+00EA U+0323 (ê + dấu nặng) | 2 |
Ba chuỗi này Unicode coi là tương đương chuẩn tắc (canonically equivalent) — cùng một chữ. Nhưng một phép so sánh đơn giản từng ký tự sẽ coi chúng khác nhau.
Bốn dạng chuẩn hoá trong Unicode
Tài liệu UAX #15 của Unicode định nghĩa bốn dạng chuẩn hoá:
| Dạng | Làm gì | Dùng cho tiếng Việt |
|---|---|---|
| NFC | Tách ra rồi gộp lại thành dạng dựng sẵn khi có thể | Dạng nên dùng để lưu, hiển thị, so sánh |
| NFD | Tách hết thành chữ gốc + dấu kết hợp, xếp dấu theo thứ tự chuẩn | Dùng tạm khi cần bỏ dấu, phân tích dấu |
| NFKC | Như NFC, thêm gộp các biến thể "tương thích" (chữ fi ghép thành f+i, số mũ ² thành 2) | Dùng khi làm chỉ mục tìm kiếm; không dùng để lưu văn bản gốc |
| NFKD | Như NFD, thêm phép tương thích | Ít dùng |
Với văn bản tiếng Việt thường, NFC và NFKC cho cùng kết quả ở phần chữ cái; khác biệt chỉ ở các ký hiệu đặc biệt. Quy tắc đơn giản: lưu bằng NFC.
Thứ tự dấu trong NFD: chi tiết hay bị bỏ qua
Khi tách, Unicode không xếp dấu theo thứ tự người Việt hay nghĩ ("mũ trước, thanh sau"), mà theo một con số gọi là lớp kết hợp (combining class). Dấu nằm dưới (dấu nặng, lớp 220) và dấu móc (lớp 216) đứng trước các dấu nằm trên (lớp 230).
| Chữ | Dạng NFD |
|---|---|
| ệ | e + dấu nặng (U+0323) + dấu mũ (U+0302) |
| ặ | a + dấu nặng (U+0323) + dấu trăng (U+0306) |
| ợ | o + dấu móc (U+031B) + dấu nặng (U+0323) |
| ễ | e + dấu mũ (U+0302) + dấu ngã (U+0303) |
| đ | đ (U+0111) — không tách |
Hai hệ quả thực tế. Thứ nhất: chuỗi "nửa dựng nửa tách" như ê + dấu nặng không phải NFC cũng không phải NFD; phải chuẩn hoá mới về đúng dạng. Thứ hai: chữ đ không bị tách thành d + gạch ngang, vì Unicode coi đ là một chữ riêng. Muốn bỏ dấu tiếng Việt bằng NFD, phải thay đ → d, Đ → D bằng tay.
Dạng tách đến từ đâu
- Tên tệp trên macOS. Hệ thống tệp của Apple từ lâu có xu hướng lưu tên tệp ở dạng tách. Tệp tên "Báo cáo tháng 11.docx" tạo trên Mac, nén lại gửi sang Windows hoặc tải lên web, có thể mang tên dạng NFD.
- Chép chữ từ một số tệp PDF. Tuỳ cách tạo PDF, chữ chép ra có thể là chữ gốc cộng dấu kết hợp.
- Bộ gõ đặt bảng mã "Unicode tổ hợp". Unikey và một số bộ gõ có tuỳ chọn này; chọn nhầm là mọi chữ gõ ra đều dạng tổ hợp.
- Dữ liệu chuyển từ Windows-1258 sang Unicode mà không chuẩn hoá.
- Nhập liệu từ nhiều nguồn — form trên web, ứng dụng điện thoại, tệp Excel của đối tác.
Triệu chứng: biết mình đang gặp NFD
| Triệu chứng | Ví dụ |
|---|---|
| Tìm kiếm không ra dù chữ nhìn y hệt | Tìm "Nguyễn" trong danh sách có "Nguyễn" mà không thấy |
| Đếm độ dài sai | Hàm LEN trong Excel trả về 6 cho chữ "Việt" thay vì 4 |
| Lọc trùng không gộp được | Hai dòng "Hà Nội" giống nhau vẫn được coi là hai giá trị |
| Dấu hiện lệch, chồng không khít | Dấu nặng lệch sang bên, dấu mũ và dấu sắc dính nhau với một số phông |
| Xoá một lần chỉ mất dấu | Ở một số trình soạn thảo, nhấn Backspace sau "ệ" còn lại "ẹ" |
| Tên tệp "trùng" mà không đè nhau | Một thư mục có hai tệp tên giống hệt nhau |
Vì sao dạng tổ hợp hay bị dấu lệch? Khi chữ dựng sẵn, nhà thiết kế phông đã vẽ sẵn hình chữ "ệ" với dấu đặt khít. Khi chữ tổ hợp, phần mềm phải tự đặt dấu nặng và dấu mũ lên chữ "e" dựa trên thông tin định vị dấu trong phông. Phông mới, làm kỹ thì kết quả gần như không khác; phông cũ hoặc phông thiếu thông tin định vị thì dấu chồng lên nhau, lệch sang bên hoặc đè vào chữ.
Chuẩn hoá về NFC: đoạn mã mang về
Người làm web, làm dữ liệu nên chuẩn hoá ngay khi nhận dữ liệu vào, trước khi lưu.
| Ngôn ngữ | Câu lệnh |
|---|---|
| Python | unicodedata.normalize('NFC', chuoi) |
| JavaScript | chuoi.normalize('NFC') |
| PHP (cần tiện ích intl) | Normalizer::normalize($chuoi, Normalizer::FORM_C) |
| Java | Normalizer.normalize(chuoi, Normalizer.Form.NFC) |
| C# (.NET) | chuoi.Normalize(NormalizationForm.FormC) |
Bỏ dấu tiếng Việt để làm đường dẫn hay tìm kiếm không dấu (Python): chuẩn hoá NFD, bỏ mọi ký tự có lớp kết hợp khác 0, rồi thay đ/Đ. Nếu quên bước thay đ, "Đà Nẵng" sẽ thành "Đa Nang" chứ không phải "Da Nang".
Người dùng văn phòng không lập trình: chuyển mã bằng bộ công cụ của bộ gõ (chọn nguồn "Unicode tổ hợp", đích "Unicode dựng sẵn"), và kiểm tra trong bộ gõ của mình rằng bảng mã đang là "Unicode" hoặc "Unicode dựng sẵn".
Bài tập: tự phát hiện chữ tổ hợp
- Mở Excel, gõ "Việt" vào ô A1, ô B1 nhập công thức =LEN(A1). Kết quả 4 là dựng sẵn.
- Tìm một tệp có tên tiếng Việt tạo trên máy Mac (hoặc chép một dòng từ PDF), dán vào ô A2, công thức =LEN(A2). Nếu số lớn hơn số chữ đếm bằng mắt, đó là dạng tổ hợp.
- Ô C1 nhập =A1=A2. Hai chữ trông giống nhau mà kết quả FALSE là đã gặp chuyện NFC – NFD.
Góc nhìn hôm nay
Đa số người dùng không bao giờ phải nghĩ đến NFC hay NFD, vì bộ gõ phổ biến mặc định dựng sẵn. Nhưng khi dữ liệu đến từ nhiều nguồn — Mac và Windows, điện thoại và web, PDF và Excel — dạng tổ hợp lặng lẽ chen vào và gây ra những lỗi "không thể giải thích": tìm không ra, lọc không gộp, sắp xếp sai. Nguyên tắc đơn giản là chuẩn hoá về NFC ngay ở cửa vào. Tài liệu UAX #15 "Unicode Normalization Forms" trên unicode.org là nguồn gốc chính xác nhất cho ai cần đi sâu.
Câu hỏi thường gặp
NFC và NFD là gì?
Đó là hai dạng chuẩn hoá Unicode. NFC gộp chữ và dấu thành ký tự dựng sẵn khi có thể, NFD tách chữ thành chữ gốc cộng các dấu kết hợp. Hai dạng hiển thị giống nhau nhưng là chuỗi khác nhau trong máy.
Văn bản tiếng Việt nên dùng NFC hay NFD?
Nên dùng NFC (Unicode dựng sẵn) để lưu, hiển thị và so sánh. Đây cũng là mặc định của các bộ gõ phổ biến. NFD chỉ nên dùng tạm khi cần xử lý dấu, ví dụ để bỏ dấu.
Vì sao tên tệp tiếng Việt từ Mac sang Windows bị lỗi?
Hệ thống tệp của Apple có xu hướng lưu tên tệp ở dạng tách (NFD). Sang Windows hoặc lên web, tên tệp có thể hiển thị dấu lệch, tìm kiếm không ra hoặc tạo hai tệp tên giống nhau.
Bỏ dấu tiếng Việt bằng NFD có cần lưu ý gì?
Chữ đ và Đ không bị tách khi chuẩn hoá NFD vì Unicode coi đó là chữ riêng. Sau khi bỏ dấu kết hợp, cần thay đ thành d và Đ thành D bằng tay.
Làm sao biết chữ đang ở dạng tổ hợp?
Dùng hàm đếm độ dài như LEN trong Excel: chữ "Việt" dựng sẵn cho 4, dạng tổ hợp cho nhiều hơn. Ở một số trình soạn thảo, xoá bằng Backspace mà chỉ mất dấu cũng là dấu hiệu của dạng tổ hợp.