Trao đổi với tôi

http://www.buidao.com

11/7/09

[Programming] Ứng dụng cơ chế Hook - Phần 2

Ứng dụng cơ chế Hook để xây dựng chương trình hỗ trợ gõ Tiếng Việt trên Hệ Điều Hành Windows 32 Bit - Phần 2

Phần II : Ứng Dụng Hook Để Xây Dựng Bộ Gõ Tiếng Việt

I.Đặt vấn đề

Nhúng tiếng Việt vào các ứng dụng chạy trên nền Windows luôn là vấn đề được nhiều người quan tâm. Và thực tế đã có khá nhiều tổ chức, cá nhân đưa ra các sản phẩm thuộc dạng này. Tính năng chủ yếu của những phần mềm này là cho phép gõ tiếng Việt trong các môi trường soạn thảo của ứng dụng. Bên cạnh đó, một số tác giả còn tích hợp thêm các chức năng khác như chuyển đổi font, kiểm tra chính tả,... Có thể thấy VietWare, VietKey là những phần mềm tiêu biểu cho lĩnh vực này, và trong thực tế, chúng đã đáp ứng được nhu cầu của phần lớn người sử dụng máy vi tính.

II.Phương án giải quyết cho font ABC (.VnTime,…)

II.1.Giới thiệu font ABC

Đây là loại font 8 bit có các ký tự được thiết kế sẵn gồm cả dấu, không giống như loại font 2 byte có các ký tự chữ và ký tự dấu riêng. Bảng mã 8 bit có tất cả 256 ký tự, trong đó có 128 ký tự chuẩn không thể sửa đổi. Tuy nhiên, do tiếng Việt có 146 ký tự nên 128 ký tự còn lại của bảng mã này không đủ chỗ, đó là lý do làm cho font ABC có một số ký tự vi phạm các ký tự điều khiển chuẩn, và phải tách ra làm 2 file font cho chữ thường và chữ hoa của cùng một loại font. Điều này là một trong những điều bất tiện trong việc sử dụng loại font này.

II.2. Ý tưởng chính

Hãy bắt đầu bằng tình huống đơn giản sau : người dùng gõ phím ‘a’, tiếp theo là phím ‘1’ và ta đang dùng kiểu gõ VNI. Điều ta mong muốn lúc này là ký tự ‘a’ trên màn hình sẽ biến mất, thay vào đó sẽ là ký tự ‘á’.

Một ý tưởng tự nhiên là cài đặt một hook WH_GETMESSAGE để hứng thông điệp WM_CHAR. Cách thực hiện như sau :

· Hứng thông điệp WM_CHAR

· Kiểm tra ký tự hiện tại là ‘1’, ký tự trước là ‘a’

· Nếu đúng :

· Dùng SendMessage gởi WM_CHAR với ký tự xoá

· Sửa ký tự ‘1’ thành ký tự ‘á’

· Nếu sai :

· Không xử lý

Ở đây phát sinh một vấn đề cần xem xét : có chắc là message WM_CHAR ta gởi sẽ đến được ứng dụng ? Với cách làm trên thì chỉ đúng với các ứng dụng đơn giản như Notepad, bởi thực chất Notepad chỉ là một EDIT_TEXT control được gắn vào một cửa sổ khung. Các thông điệp được gởi sẽ trực tiếp đến ứng dụng. Còn đối với các môi trường soạn thảo lớn và phức tạp hơn như WordPad hay Microsoft Word, chúng nhận thông điệp từ các sự kiện của người dùng qua nhiều cấp, do đó khi ta gởi message đến, không có gì đảm bảo message của chúng ta sẽ đến chính xác nơi cần đến.

Còn bây giờ hãy xét cách tiếp cận sau đây : thay vì dùng các hàm để gởi thông điệp đến ứng dụng, ta sẽ làm phát sinh thông điệp một cách tự nhiên hơn, rồi sau đó thực hiện sự sửa đổi trên thông điệp để được ký tự có dấu mong muốn. Vẫn xét tình huống trên, cách làm bây giờ như sau :

· Hứng message WM_KEYDOWN

· Kiểm tra phím hiện tại là ‘1’, phím trước là ‘a’ (giá trị mã phím ảo)

· Nếu đúng :

· Sửa phím ‘1’ thành phím xóa (mã phím ảo VK_BACK).

· Nếu sai :

· Không xử lý.

· Hứng message WM_CHAR

· Kiểm tra ký tự hiện tại là ký tự xoá (có mã 8)

· Nếu đúng:

·Dùng keybd_event để giả lập nhấn phím SpaceBar

·Nếu sai :

· Không xử lý

· Kiểm tra ký tự hiện tại là ký tự khoảng trắng (có mã 32)

· Nếu đúng :

· Sửa ký tự này thành ký tự ‘á’.

· Nếu sai :

·Không xử lý

Tất nhiên quá trình trên cần có một số thao tác với cờ hiệu để phân biệt khi nào phím BackSpace, SpaceBar được người dùng nhấn, khi nào 2 phím này là do chương trình của ta gởi đến. Tuy nhiên, để dễ thấy được ý tưởng chủ đạo của giải thuật vừa nêu, ta đã không đưa vào các thao tác này ở phần trên.

Giải thích :

Ý tưởng chính xuyên suốt giải thuật trên là thao tác giả lập gõ phím để phát sinh thông điệp trung gian, sau đó hứng lại thông điệp trung gian và thực hiện sửa đổi để có được ký tự mong muốn. Phím được chọn để giả lập trong trường hợp này là SpaceBar (Về nguyên tắc có thể chọn bất kỳ phím nào cho mục đích này, miễn là phím đó khi gõ có thể cho ta message WM_CHAR với mã ký tự hợp lệ).

Có thể hình dung sự hoạt động của giải thuật trên như sau :

· Khi người dùng gõ phím ’a’, Filter Function không làm gì cả.

· Khi phím ‘1’ được gõ, điều kiện ở nhánh chặn message WM_KEYDOWN được thỏa, phím ‘1’ chuyển thành phím xóa (mã phím ảo VK_BACK) . Chính phím xóa này sẽ thực hiện thao tác xóa chữ ‘a’ trên màn hình soạn thảo.

· WM_KEYDOWN đã được sửa đổi ở trên được chuyển thành WM_CHAR với mã ký tự 8, thoả điều kiện ở nhánh hứng WM_CHAR, SpaceBar được nhấn nhờ hàm keybd_event.

· Thông điệp WM_KEYDOWN do phím SpaceBar sinh ra không bị chặn, tiếp tục chuyển thành WM_CHAR với mã ký tự 32. Lúc này, nó được Filter Function hứng lại, và sau khi kiểm tra các điều về cờ hiệu, Filter Function chuyển mã ký tự của message này thành mã ky tự của chữ ‘á’. Kết quả ta có là chữ ‘a’ bị xóa và chữ ‘á’ sẽ xuất hiện trên màn hình.

Rõ ràng cách làm này an toàn hơn so với cách đầu tiên, bởi lẽ message được phát sinh y hệt như khi ta gõ phím thực sự (do được giả lập bằng hàm keybd_event), và chuyện lấy được các message này là chuyện của ứng dụng (bởi ta đã làm phát sinh message ở mức thấp), ta không cần phải can thiệp bằng lệnh SendMessage như phương án đầu tiên.

Để thực hiện điều này, dĩ nhiên ta phải tổ chức Filter Function của hook WH_GETMESSAGE để có thể hứng được cả 2 thông điệp WM_KEYDOWN và WM_CHAR.

II.3.Giải thuật chi tiết và cài đặt

Dễ nhận thấy việc đầu tiên là phải ghi nhận các mã ký tự của các ký tự có dấu trong font ABC để thực hiện việc sửa đổi thông điệp sau này.

Ta sẽ dùng tiện ích Character Map trong bộ System Tool của Windows để ghi nhận các ký tự và mã tương ứng của chúng, sau đó lưu các mã này vào một mảng toàn cục được khai báo trong DLL, lấy tên TCVN3 chẳng hạn. Nên tổ chức mảng này theo một thứ tự nhất định, ví dụ các mã của các ký tự có dấu của cùng một nguyên âm gốc nên đi liên tiếp nhau. Sau đó, phải ghi nhớ các chỉ số của các phần tử trong mảng tương ứng với từng ký tự, ví dụ phần tử mảng có giá trị là mã của chữ ‘á’ sẽ có chỉ số là bao nhiêu…

Đến đây ta cần một biến toàn cục nữa, lấy tên Index chẳng hạn, để lưu các giá trị chỉ số vừa nêu khi người dùng nhấn phím, và các phím này nằm trong tầm vực bỏ dấu của chương trình. Ví dụ, khi họ gõ ‘a’, sau đó gõ ‘1’ thì giá trị của Index là chỉ số của phần tử mảng chứa mã ký tự của chữ ‘á’.

Cách làm này sẽ tiện lợi khi ta thực hiện sửa đổi trên thông điệp WM_CHAR, rõ ràng lúc này giá trị wParam mới của message sẽ là TCVN3[Index].

Khi cài đặt hook chặn message, nhánh chặn WM_KEYDOWN sẽ làm nhiệm vụ kiểm tra phím và xác định Index, chính nhánh hứng message WM_CHAR mới thực sự thi hành tác vụ sửa đổi thông tin trên thông điệp để sinh ra các ký tự có dấu.

Sửa dấu trong khi gõ

Với những thông tin trên , ta đã giải quyết được tình huống nêu ra ở phần đầu của giải thuật. Tất nhiên khi cài đặt ta sẽ áp dụng tổng quát cho các nguyên âm và các phím có thể sinh dấu từ ‘1’ đến ‘5’ (ta gọi phần này là Module_1) .

Nhưng nếu người dùng muốn sửa dấu trong khi gõ thì sao ? Ví dụ, họ muốn có chữ ‘à’, nhưng các phím đã vô tình bị gõ là ‘a’ và ‘1’, lúc này trên màn hình xuất hiện chữ ‘á’, bây giờ khi họ gõ ‘2’, chữ ‘á’ phải bị xoá và thay vào đó là chữ ‘à’. Ta hãy xem giải pháp cho bài toán này :

· Hứng message WM_KEYDOWN

· Kiểm tra nếu phím hiện tại (CurrentKey) là các phím từ ‘1’ đến ‘5’, phím trước (Prev1Key) cũng cùng dạng này, và phím trước nữa (Prev2Key) là nguyên âm.

· Nếu đúng :

· Sửa phím hiện tại thành phím xóa (mã phím ảo VK_BACK)

· Giả lập gõ 2 phím sau :

· Prev2Key

· CurrentKey

· Nếu sai :

· Không xử lý

Giải thích (Xét trong ngữ cảnh tình huống nêu trên):

Phím ‘2’ được sửa thành phím xóa sẽ thực hiện xóa ký tự ‘á’ trên màn hình. Sau đó 2 phím được giả lập sẽ tạo ra chữ ‘à’ như ta mong muốn. Ở dây ta thấy rõ hơn ý tưởng hứng lại sự kiện để xử lý : chỉ cần xoá ký tự sai, và giả lập gõ chính xác để tạo ký tự đúng. Lúc này chính Module_1 ta đã viết sẽ tiếp nhận các sự kiện giả lập và nó lại thi hành các tác vụ như ta đã mô tả : kiểm tra điều kiện để xác định Index, sửa message,… để cho ra ký tự mà người dùng muốn gõ.

II.4. Áp dụng cho các luật bỏ dấu khác

Với các ý tưởng trên, các luật bỏ dấu, sửa dấu trong khi gõ khác (bỏ dấu ô, ơ, ư, â, ă, ấ,…) được thực hiện với cách thức hoàn toàn tương tự. Có khác chăng là các điều kiện kiểm tra giá trị các phím trước phím hiện tại để xác định Index cho phù hợp.

II.5. Ưu và nhược điểm của giải pháp

Ta có thể nhận ra ưu điểm của giải pháp này ở tính đơn giản và an toàn của nó. Đơn giản trong quá trình cài đặt, mang tính tái sử dụng ; an toàn trong quá trình phát sinh các message cho ứng dụng.

Tuy nhiên, cách làm này chỉ áp dụng được dễ dàng cho bài toán bỏ dấu, sửa dấu ngay sau nguyên âm mà thôi. Còn đối phó với vấn đề bỏ dấu tự do (bỏ dấu theo vần, theo từ), phương án này cần các thao tác kiểm tra rất phức tạp trên tập các giá trị phím trước, để xác định chính xác vị trí bỏ dấu. Bởi lẽ, nó phải duyệt trên giá trị của các mã phím ảo để xác định các nguyên âm có dấu, và điều này là rất khó khăn. Ta sẽ có một cách tiếp cận khác ưu việt hơn cho bài toán này ở phần kế sau đây, cách tiếp cận thao tác trực tiếp tên các ký tự.

III.Phương án giải quyết cho font VNI-Win (VNI-Times,...)

III.1.Cấu trúc font VNI-Win

Khác với các font ABC(font 1 byte) có dấu và các nguyên âm được tổ hợp trong cùng một ký tự, font VNI-Win(font 2 byte) có dấu và các nguyên âm rời nhau. chẳng hạn ký tự ‘á’ là sự tổ hợp của hai ký tự ‘a’ và dấu sắc. Tuy nhiên, có một số ký tự do có cấu trúc đặt biệt nên dấu và ký tự được tổ hợp trong một ký tự như: đ, Đ, í, ì, ỉ, ĩ, ị, ỵ, Í, Ì, Ỉ, Ĩ, Ị, Ỵ, ư, ơ, Ư, Ơ.

Chúng ta hãy bước vào phần sau để xem xét các phương án cho bộ gõ tiếng việt dùng cho font VNI-Win.

III.2.Kiểu gõ dấu tại chổ

Như đã đề cập ở phần trước, để xây dựng bộ gõ cho các phần mềm trong môi trường Windows ta sẽ cài đặt một hàm Hook có chức năng chặn ngắt bàn phím ở mức toàn cục. Và khi đó tất cả các phím nhấn từ bàn phím sẽ được chương trình gõ phím của chúng ta xử lý trước khi chúng được gởi tới ứng dụng. Như vậy để gõ được tiếng Việt trong các dụng dụng đang thi hành trong môi trường trong hàm Hook phải thực hiện thao tác thay đổi các ký tự khi người dùng gõ vào, nhờ đó ứng dụng sẽ nhận được các ký tự có dấu tiếng Việt.

Giải thuật

Ta sẽ cài đặt hàm Hook WH_GETMESSAGE hứng thông điệp WM_KEYDOWN và WM_CHAR để xử lý. Nhắc lại, khi người dùng nhấn phím ta sẽ lần lượt nhận được các thông điệp WM_KEYDOWN, WM_KEYUP, WM_CHAR. Chương trình ta sẽ xử lý ở thông điệp WM_KEYDOWN và WM_CHAR.

Nguyên tắc khi người dùng nhấn một phím nằm trong phạm vi là phím bỏ dấu(có khả năng là phím dấu), 0-9 đối với kiểu gõ VNI và “s,f,r,x,j,a,o,e,w,d,z” đối với kiểu gõ TELEX. Ta xét ký tự trước đó, có 3 khả năng xảy ra:

· Nếu là ký tự có thể bỏ dấu:(a,e,i,o,u,y,d) nếu dấu phù hợp với kí tự cần bỏ dấu (ký tự liền trước) ta tiến hành bỏ dấu.

· Nếu là một ký tự dấu rời hoặc một ký tự nguyên âm có dấu( chẳng hạn như “í” chỉ là một ký tự còn “á” là hai ký tự) ta xét là nên sửa dấu hay lập dấu và sau đó điều chỉnh lại cho phù hợp.

· Nếu là một phụ âm thì ta không xử lý gì cả.

Lưu ý ở đây có điểm khác biệt giửa font 1 byte và font 2 byte, khi bỏ dấu chẳng hạn chử “á” font 1 byte sẽ xóa ký tự “a” trước đó bằng cách sửa ký tự “1” vừa nhận được thành ký tự xóa VK_BACK và sau đó gởi một ký tự “á” còn font 2 byte sẽ gởi 2 ký tự “a” và dấu sắc sau khi xóa ký tự “a” trước đó.

Bài toán lúc này trở nên đơn giản hơn và vấn đề lúc này là chương trình sẽ xử lý dấu như thế nào cho đơn giản và chính xác. Trước tiên ta cần phân biệt xử lý đối với các lọai dấu khác nhau:

- loại 1: gồm các dấu: sắc, huyền, hỏi, ngã, nặng.

- loại 2: gồm các dấu: dấu “^” và dấu “ă”.

- lọai 3: gồm các dấu: dấu “^” kết hợp với một dấu loại 1(ví dụ: “”, “”, ”...)

- loại 4: gồm các dấu: dấu “ă” kết hợp với một dấu loại 1(ví dụ: “”, “”, “”...”)

Bài toán xử lý gõ tiếng việt cho font chữ 2 byte theo kiểu gõ cho phép bỏ dấu ngay đã được giải quyết thành công.

Hàm Hook xử lý cho bài toán gõ tiếng việt font 2 byte kiểu gõ bỏ dấu tại chổ.

EXPORT LRESULT CALLBACK HookProc2ByteNormalVNI(int nCode, WPARAM wParam, LPARAM lParam)

{

tagMSG *pMsg;

if (nCode>=0 )

{

pMsg = (MSG*)lParam;

if (wParam == PM_REMOVE)

{

if (pMsg->message == WM_KEYDOWN)

{

if (InScope(pMsg->wParam)||

(pMsg- >wParam==VK_NUMPAD0&&DeleteTwo))

{

switch (pMsg->wParam)

{

case VK_1:

case VK_2:

case VK_3:

case VK_4:

case VK_5:

ProcessSign1(pMsg->wParam-VK_0);

if (Del)

pMsg->wParam = 8;

break;

case VK_6:

case VK_8:

ProcessSign2(pMsg->wParam-VK_0);

if (Del)

pMsg->wParam = 8;

break;

case VK_7:

if Prev1Key==VK_o||Prev1Key==VK_O||

Prev1Key==VK_u||Prev1Key==VK_U)

{

pMsg->wParam = 8;

Del = TRUE;

Flag = TRUE;

switch (Prev1Key)

{

case VK_o:

NextKey = o7;

break;

case VK_O:

NextKey = O7;

break;

case VK_u:

NextKey = u7;

break;

case VK_U:

NextKey = U7;

break;

}

}

break;

case VK_9:

if (Prev1Key==VK_d||Prev1Key==VK_D)

{

pMsg->wParam = 8;

Del = TRUE;

Flag = TRUE;

if (Prev1Key == VK_d)

NextKey = a9;

else NextKey = A9;

}

break;

case VK_NUMPAD0:

if (DeleteTwo)

{

pMsg->wParam = 8;

Del = TRUE;

Flag = TRUE;

DeleteTwo = FALSE;

}

}

}

}

if (pMsg->message == WM_CHAR)

{

if (Flag)

{

switch (pMsg->wParam)

{

case VK_1:

case VK_2:

case VK_3:

case VK_4:

case VK_5:

case VK_6:

case VK_7:

case VK_8:

case VK_9:

if (!Del)

{

pMsg->wParam = NextKey;

Flag =FALSE;

}

break;

case VK_BACK:

if (Del)

keybd_event(VK_NUMPAD0, 0x45, KEYEVENTF_EXTENDEDKEY, 0);

break;

case VK_0:

if (Del)

{

pMsg->wParam = NextKey;

Flag = FALSE;

}

break;

}//of switch

}//of if (Flag)

if (!Del) // Normal

{

Prev2Key = Prev1Key;

Prev1Key = pMsg->wParam;

}

else

{

Prev1Key = NextKey;

Del=pMsg->wParam ==8;

}

}//of if (pMsg->message==WM_CHAR)

}

} // of if (nCode >= 0)

return CallNextHookEx(hhook, nCode, wParam, lParam);

}

III.3.2.Kiểu gõ dấu cải tiến:

Khi gõ theo kiểu bỏ dấu tại chổ có những mặt hạn chế: thứ nhất cách gõ không được tự nhiên thoải mái như khi viết và bỏ dấu bằng tay, thứ hai rất khó cho người dùng sửa dấu sau khi đã đánh sang ký tự khác, thứ ba có thể người dùng sẽ bỏ dấu không đúng chính tả hay không đúng vần. Vấn đề dặt ra ờ đây là làm thế nào để hỗ trợ được những tính năng khiếm khuyết trên.

Giải pháp gõ dấu sau

Một ví dụ cụ thể về gõ dấu sau, chẳng hạn như khi người dùng gõ từ “làng” thì họ có thể gõ theo nhiều cách khác nhau như: la2ng, lan2g, lang2.

· Giải pháp ban đầu

Giải pháp ban đầu được đưa ra là sử dụng Clipboard, sau khi người dùng gõ một phím có khả năng là phím dấu ta giả lập một phím Shitf và các phím mủi tên ß để đánh dấu khoảng 8 ký tự vừa gõ, sau đó giả lập tổ hợp phím Ctrl X để cắt chuổi 8 ký tự được gõ trước ký tự hiện hành vào Clipboard, tiếp theo ta sẽ lấy dữ liệu ra từ Clipboard và đổ vào mảng ký tự và xử lý dấu trên chúng, dữ liệu được xử lý xong được đưa vào Clipboard, cuối cùng ta sẽ giả lập một tổ hợp phím Ctrl V để dán chuổi vừa xử lý ra màn hình ứng dụng.

Việc áp dụng Clipboard theo hương tiếp cận này dẫn đến nhiều khiếm khuyết và khó khăn khi xử lý. D không phải tất cả các ứng dụng đều dùng phím Shift và các phím mủi tên để đánh dấu, và dùng phím Ctrl X, Ctrl V để cắt và dán. Hơn nửa, mổi khi một phím trong phạm vi xử lý dấu được nhấn thì ứng dụng phải thực hiện đồng thời 2 thao tác cắt và dán, việc này làm cho chương trình trở chậm chạp, và màn hình của ứng dụng chớp, nhấp nháy trong vùng bị cắt và dán lại. Một ý kiến hay là chỉ nên áp dụng Clipboard cho trường hợp dùng Sửa dấu nhanh(Chức năng này hổ trợ cho người sử dụng khi họ đã hoàn tất văn bản và kiểm tra lại văn bản từ đầu đến cuối một lần và sửa lỗi).

· Giải pháp khác

Một giải pháp khác là dùng một buffer thay thế cho Clipboard, tại mỗi thời điểm buffer sẽ lưu giữ giá trị của 8 ký tự(từ dài nhất) trước đó. Khi người dùng gõ một ký tự trong phạm vi bỏ dấu, ta sẽ cập nhật lại buffer cho phù hợp, xóa một số ký tự cần thiết và xuất một số ký tự đã được cập nhật trong buffer.

Ví dụ:

Khi gõ : lang giá trị buffer sẽ là:

l

a

n

g


Khi gõ thêm ký tự: 2, có nghĩa là bỏ dấu huyền. buffer sẽ được cập nhật lại như sau:

l

a

\

n

g


Và khi đó ta phải xóa ngược lại 3 ký tự(xóa “ang”) và xuất ra 4 ký tự cuối của buffer(xuất “àng”).

Như vậy mổi lần người dùng gõ dấu ta phải hiệu chỉnh lại buffer và xác định số ký tự cần phải xóa và số ký tự cần xuất ra từ buffer(tính từ vị trí cuối cùng).

Đối với các ký tự khác không phải bỏ dấu thì ta không xử lý nhưng vẫn phải cập nhật lại trong buffer.

Ví dụ:

Các trạng thái của buffer khi gõ chữ “làng”.

Gõ “l

l

Gõ “a

l

a

Gõ “n

l

a

n

Gõ “g

l

a

n

g

Gõ “2

l

a

\

n

G

Lúc này DelNum = 3(số ký tự cần xóa) và OutNum =4(số ký tự cần xuất).

Đến đây thì vấn đề gõ dấu sau được giải quyết khá hoàn hảo. Do sử dụng buffer nên chương trình xử lý nhanh hơn, và tại mổi thời điểm chỉ xóa đi số ký tự tố thiểu cần thiết nên giúp cho màn hình ứng dụng không bị nhấp nháy.

· Nẩy sinh vấn đề

Tuy giải pháp trên giải quyết rất tốt vần đề đặt ra nhưng bên cạnh đó lại nẩy sinh ra một khó khăn khác là làm thế nào để xác định vị trí bỏ dấu trong một vần tiếng việt, đây là vấn đề khó khăn và phức tạp bởi vì trong tiếng việt không có quy luật bỏ dấu cụ thể, chuẩn mực. Cuối cùng đưa đến việc xây dựng bộ luật bỏ dấu dành cho tiếng Việt.

· Bộ luật bỏ dấu tiếng Việt

Do trong tiếng việt dấu chỉ được bỏ ở các vị trí của các nguyên âm nên bộ luật sẽ được xây dựng dựa trên các nguyên âm, chú ý rằng trong một từ tiếng việt các nguyên âm luôn đi liền nhau và số lượng nguyên âm trong một từ không thể quá 3 nguyên âm. Với hai nhận xét trên giúp ta có thể xây dựng bộ luật bỏ dấu tiếng Việt một cách dễ dàng hơn.

Phân chia các từ theo số lượng nguyên âm:

Các từ có 1 nguyên âm:

Nếu ký tự đứng trước nguyên âm là ký tự “q” hoặc “p” thì không được phép bỏ dấu.

Ngược lại dấu sẽ được bỏ tại vị trí nguyên âm duy nhất này.

Các từ có 2 nguyên âm:

Nguyên âm đầu: a

A

vị trí bỏ dấu

AE

không có

AI

A

AO

A

AU

A

AY

A

Nguyên âm đầu: e

E

vị trí bỏ dấu

EA

không có

EI

không có

EO

E

EU

E

EY

không có

Nguyên âm đầu: i

I

vị trí bỏ dấu

IA

I

IE

E

IO

không có

IU

I

IY

không có

Nguyên âm đầu: o

O

vị trí bỏ dấu

OA

O

OE

O

OI

O

OU

không có

OY

không có

Nguyên âm đầu: u

U

vị trí bỏ dấu

UA

U

UE

E

UI

U

UO

O

UY

U

Nguyên âm đầu: y

Y

vị trí bỏ dấu

YA

Không có

YE

Không có

YI

Không có

YO

Không có

YU

Không có

Theo sơ đồ xử lý bên dưới.

Các từ có 3 nguyên âm:

Có các tổ hợp 3 nguyên âm sau:

ươu rượu

ieu liễu

oai hoài

uay khuấy

uoi muỗi

uya khuya

uye khuyên

yeu* yêu

(*Đây là dạng đặt biệt được sử lý riêng, vần yêu chỉ đúng trong trường hợp đứng riêng lẻ không đi cùng với phụ âm đứng trước)

Thông thường các từ có 3 nguyên âm thường được bỏ dấu ở nguyên âm giữa, chỉ trừ trường hợp vần “uyê” được bỏ dấu ở nguyên âm cuối.

Trường hợp đặc biệt nếu bắt đầu bằng “qu” hoặc “gi” thì dấu sẽ bỏ ở một trong hai nguyên âm sau được xử lý như từ có hai nguyên âm.

SƠ ĐỒ XỬ LÝ DẤU CHO CÁC TỪ CÓ 2 NGUYÊN ÂM:

· Nếu nguyên âm đầu là:

a” thì

nếu nguyên âm sau là “a”,”e”:không bỏ dấu

ngược lại:bỏ dấu tại nguyên âm đầu a.

e” thì

nếu nguyên âm sau là “o”,”u”:bỏ dấu tại nguyên âm đầu e.

ngược lại:không bỏ dấu

i” thì

nếu trước “i” là phụ âm “g”:bỏ dấu ở nguyêm âm sau.

ngược lại

nếu nguyên âm sau là “i”,“o”,”y”:không bỏ dấu

ngược lại

nếu nguyên âm sau là “a”,”u”:bỏ dấu ở nguyên âm đầu.

ngược lại:bỏ dấu ở nguyên âm sau

o” thì

nếu nguyên âm sau là “o”,”u”,”y”:không bỏ dấu

ngược lại:bỏ dấu ở nguyên âm đầu.

u” thì

nếu trước “u” là phụ âm “q”:bỏ dấu ở nguyêm âm sau.

ngược lại

nếu nguyêm âm sau là “a”,”i”,”y”:bỏ dấu ở nguyên âm đầu

ngược lại

nếu nguyên âm sau là “e”,”o”:bỏ dấu ở nguyên âm sau

ngược lại:không bỏ dấu.

y” thì không bỏ dấu.

Vấn đề giải quyết được có vẻ phức tạp hơn nhưng với bộ luật xử lý như trên ta có thể cài đặt bộ gõ có chức năng kiểm tra lổi chính tả, không cho phép gõ những từ sai lỗi chính tả và có khả năng bỏ dấu sau rất tốt. Chắc bạn sẽ rất ngạc nhiên nếu như tôi đưa ra một vấn đề vướng mắc khác cho giải pháp gần như hoàn hảo này. Thật vậy, khi bạn gõ “hoa” bạn gõ thêm dấu huyền dĩ nhiên bộ gõ này sẽ bỏ dấu ngay nguyên âm “o” trở thành “hòa” hoàn toàn chính xác. Nhưng điều gì sẽ xảy ra nếu bạn gõ thêm “n” nó sẽ trở thành “hòan” đây là một điểm mà một số bộ gõ phím như VietWare, VietKey,... và các bộ gõ phím thông dụng khác chưa xử lý.

Vì vậy để chương trình gõ phím dễ dùng, tiện dụng và thông minh hơn, bạn nên cài thêm chức năng sửa dấu để điều chỉnh dấu khi phát hiện dấu đã được bỏ sai vị trí.Điều này cũng được thực hiện dễ dàng nhờ vào bộ luật mà chúng ta đã xây dựng từ trước nhưng phải kết hợp thêm một số điều kiện ngữ cảnh, có nghĩa là việc xác định vị trí bỏ dấu không chỉ phụ thuộc vào các nguyên âm mà còn phụ thuộc vào các phụ âm trước hoặc sau các nguyên âm đó.