Chữ Việt Unicode trong lua

lua xử lý chuỗi theo từng byte trong khi chữ Việt mã hóa theo UTF8 chiếm từ 1 đến 4 byte. Giải pháp là tìm kiếm một thư viện hỗ trợ chữ Việt Unicode cho lua.

Có nhiều thư viện giúp lua xử lý chuỗi unicode được đặt tên utf8.lua, thuộc nhiều tác giả khác nhau nhưng nội dung gần như giống hệt nhau chỉ khác đôi chút ở cách trình bày. Trong số đó có thể thư viện đầy đủ nhất là ở link này

  1. Các hàm xử lý chuỗi có phiên bản UTF8:
len
sub
reverse
char
unicode
gensub
byte
find
match
gmatch
gsub
dump
format
caseless
lower
upper
rep

2. Riêng các hàm caseless, lowerupper cần phải có file utf8data.lua. Trong file này có hai bảng chuyển đổi hoa/thường của các ký tự unicode, nhiều hơn các nguyên âm chữ Việt, vì vậy có thể viết lại để giảm kích thước/bộ nhớ (2-3KB so với 33KB).

utf8data.lua

utf8_lc_uc = {
    ["a"] = "A",   ["b"] = "B",   ["c"] = "C",   ["d"] = "D",   ["e"] = "E",   ["f"] = "F",   ["g"] = "G",   ["h"] = "H",   ["i"] = "I",
    ["j"] = "J",   ["k"] = "K",   ["l"] = "L",   ["m"] = "M",   ["n"] = "N",   ["o"] = "O",   ["p"] = "P",   ["q"] = "Q",   ["r"] = "R",
    ["s"] = "S",   ["t"] = "T",   ["u"] = "U",   ["v"] = "V",   ["w"] = "W",   ["x"] = "X",   ["y"] = "Y",   ["z"] = "Z",
    ["à"] = "À",   ["á"] = "Á",   ["ã"] = "Ã",   ["ả"] = "Ả",   ["ạ"] = "Ạ",
    ["ă"] = "Ă",   ["ằ"] = "Ằ",   ["ắ"] = "Ắ",   ["ẳ"] = "Ẳ",   ["ẵ"] = "Ẵ",   ["ặ"] = "Ặ",
    ["â"] = "Â",   ["ấ"] = "Ấ",   ["ầ"] = "Ầ",   ["ẩ"] = "Ẩ",   ["ẫ"] = "Ẫ",   ["ậ"] = "Ậ",
    ["è"] = "È",   ["é"] = "É",   ["ẽ"] = "Ẽ",   ["ė"] = "Ė",   ["ẹ"] = "Ẹ",
    ["ê"] = "Ê",   ["ế"] = "Ế",   ["ề"] = "Ề",   ["ể"] = "Ể",   ["ễ"] = "Ễ",   ["ệ"] = "Ệ",
    ["ì"] = "Ì",   ["í"] = "Í",   ["ĩ"] = "Ĩ",   ["ỉ"] = "Ỉ",   ["ị"] = "Ị",
    ["ò"] = "Ò",   ["ó"] = "Ó",   ["õ"] = "Õ",   ["ỏ"] = "Ỏ",   ["ọ"] = "Ọ",
    ["ơ"] = "Ơ",   ["ớ"] = "Ớ",   ["ờ"] = "Ờ",   ["ở"] = "Ở",   ["ỡ"] = "Ỡ",   ["ợ"] = "Ợ",
    ["ô"] = "Ô",   ["ố"] = "Ố",   ["ồ"] = "Ồ",   ["ổ"] = "Ổ",   ["ỗ"] = "Ỗ",   ["ộ"] = "Ộ",
    ["ù"] = "Ù",   ["ú"] = "Ú",   ["ủ"] = "Ủ",   ["ũ"] = "Ũ",   ["ụ"] = "Ụ",
    ["ư"] = "Ư",   ["ứ"] = "Ứ",   ["ừ"] = "Ừ",   ["ử"] = "Ử",   ["ữ"] = "Ữ",   ["ự"] = "Ự",
    ["ý"] = "Ý",   ["ỳ"] = "Ỳ",   ["ỷ"] = "Ỷ",   ["ỹ"] = "Ỹ",   ["ỵ"] = "Ỵ",
    ["đ"] = "Đ"
}
utf8_uc_lc = {}
for key,value in pairs(utf8_lc_uc) do
   utf8_uc_lc[value] = key
end
}

3. Trong ngôn ngữ lua, tìm kiếm chuỗi luôn phân biệt hoa/thường, và không có khái niệm tìm trọn từ (whole word). Chúng ta thêm vào file utf8.lua hàm caseless giúp tạo ra mẫu tìm kiếm không phân biệt hoa/thường.

local function utf8caseless (str)
   local ret = ''
   length = utf8len (str)
   for i=1, length do
      c = utf8sub (str, i, i)
      lo= utf8lower (c)
      up= utf8upper (c)
      if lo == up then
         ret = ret .. c
      else
         ret = ret .. '[' .. lo .. up .. ']'
      end
   end
   return ret
end

và thêm vào gần cuối file dòng

utf8.caseless = utf8caseless

4. Tóm lại, để sử dụng chữ Việt unicode, chúng ta cần 2 file utf8.luautf8data.lua. Các file này có thể để trong thư mục tùy ý, thí dụ /mnt/tool/.lua và thông báo cho lua khi sử dụng

package.path = package.path .. ';/mnt/tool/.lua/?.lua'
require "utf8data"
utf8 = require "utf8"
print (utf8.match ("Hôm nay tôi đi học!",  '[%s%p](' .. utf8.caseless('Đi học') .. ')[%s%p]') )
--> đi học

Chú ý là chúng ta tìm không phân biệt hoa thường và tìm trọn từ. Mẫu tìm kiếm ‘ôi‘ hay ‘họ‘ sẽ cho kết quả nil

5. Trong utf8.lua có hàm binsearch (binary search), có thể chỉnh sửa đôi chút cho phù hợp để sử dụng cho nhiều dạng so sánh chuỗi khác nhau

function binsearch(sortedTable, item, comp)
   local head, tail = 1, #sortedTable
   local mid = math.floor((head + tail)/2)
   local s
   if type(comp) ~= "function" then
      comp = function(s) return s end
   end
   while (tail - head) > 1 do
      if comp(sortedTable[tonumber(mid)]) > item then
         tail = mid
      else
         head = mid
      end
      mid = math.floor((head + tail)/2)
   end
   if comp(sortedTable[tonumber(head)]) == item then
      s = tonumber(head)
   elseif comp(sortedTable[tonumber(tail)]) == item then
      s = tonumber(tail)
   else
      return nil
   end
   local e = s
   while s >= 1 do
      if comp(sortedTable[s]) == item then s = s - 1 else break end
   end
   while e <= #sortedTable do
      if comp(sortedTable[e]) == item then e = e + 1 else break end
   end
   return s+1, e-1
end

sortedTable là bảng đã được sắp xếp theo tiêu chuẩn so sánh comp. Thí dụ binsearch tìm kiếm theo độ dài chuỗi thì sortedTable xếp thứ tự theo độ dài chuỗi. Hàm binsearch trả lại index đầucuối nếu tìm thấy và nil nếu không tìm thấy.

table.sort (sortedTable, function (a, b) return utf8.len(a) < utf8.len(b) end)
return binsearch (sortedTable, 5, function (a) return utf8.len(a) end)

Nếu danh sách keyword.csv lớn, để biết chuỗi str có nằm trong danh sách hay không, chúng ta dò tìm theo độ dài của str trước sau đó mới dò tìm từ khóa trong quảng từ khóa cùng độ dài với str.

UTF8 trong LUA 5.3.x

LUA version 5.3 trở đi đã có những hàm chuỗi hỗ trợ unicode utf8, tuy rằng chỉ có những hàm hết sức cơ bản.

Để có những công cụ cần thiết hỗ trợ utf8, cần phải viết thêm bằng ngôn ngữ lua hay C. Tuy viết bằng ngôn ngữ lua nhanh và dễ nhưng chạy chậm, thậm chí rất chậm thí dụ như khi viết hàm user function cho sqlite3.

Một thư viện utf8 viết bằng C, tương thích với lua 5.3, có thể tìm thấy ở đây. Điểm đặc biệt là có thể dùng file nguồn của thư viện này chép vào bộ nguồn của lua 5.3 và biên dịch, khi đó các hàm utf8 được bổ sung phong phú và chạy rất nhanh.

Leave a Comment

Filed under Software

Leave a Reply