Hàm PHP dùng để kiểm tra một từ nào đó có cần chuyển vị trí dấu thanh hay không

Phiên bản nâng cấp cho hàm chuyển dấu tiếng Việt đã có ở đây: Viết lại hàm PHP chuyển dấu thanh cho từ tiếng Việt (v1.2)


Trong bài viết này tôi sẽ nói về 2 hàm bắt lỗi thả dấu thanh trong tiếng Việt. Đầu vào là từ đơn. Trong trường hợp bạn bắt lỗi một chuỗi thì cần tách nó ra rồi đưa vào hàm.

Hàm thứ nhất sử dụng Regex, nó như sau:

  • Biến đầu vào được đặt tên là $str;
  • Biến $bat_loi mặc định để là 0 (không có lỗi). Khi so khớp thấy lỗi biến này mới chuyển thành 1;
  • Từ $r đến $r8 là các biểu thức chính quy regex để bắt lỗi dấu trong từng trường hợp, trong mã mẫu tôi có chú thích để dễ hiểu hơn;
  • Cuối mã là phần kiểm tra với câu lệnh điều kiện if, chỉ cần so khớp chính xác một trong 8 biểu thức là nó trả về kết quả dấu thanh đang đặt sai;

PS: tiếng Việt hiện chưa thống nhất quy tắc đặt dấu thanh, trong mã bên dưới tôi sử dụng quy tắc phổ biến hơn.

$str = "hùynh";

function kiem_tra_dau_loi($str) {
$bat_loi=0;

// bắt lỗi gío, qúa, qúy-----------------

$r='/^(gí|gì|gỉ|gĩ|gị]|qú|qù|qủ|qũ|qụ|qứ|qừ|qử|qữ|qự)(a|ă|â|e|ê|o|ô|ơ|u|ư|i|y)([a-z]*)/'; 

//---------------------------------------

// bắt lỗi toà, keó, hoạ-----------------------------

$r2='/[bcdfhjklmnprstvxzđ]*[^gq](a|ă|â|e|o|ô|ơ|u|ư|i|y)(á|à|ả|ã|ạ|ắ|ằ|ẳ|ẵ|ặ|ấ|ầ|ẩ|ẫ|ậ|é|è|ẻ|ẽ|ẹ|ó|ò|ỏ|õ|ọ|ố|ồ|ổ|ỗ|ộ|ú|ù|ủ|ũ|ụ|ứ|ừ|ử|ữ|ự|ý|ỳ|ỷ|ỹ|ỵ|í|ì|ỉ|ĩ|ị)$/'; 

//---------------------------------------------------

// bắt lỗi tòan, cừơi, hòang-------------

$r3='/[bcdfhjklmnprstvxzđ]*[^aăâeoôơuưiy](á|à|ả|ã|ạ|ắ|ằ|ẳ|ẵ|ặ|ấ|ầ|ẩ|ẫ|ậ|é|è|ẻ|ẽ|ẹ|ó|ò|ỏ|õ|ọ|ố|ồ|ổ|ỗ|ộ|ú|ù|ủ|ũ|ụ|ứ|ừ|ử|ữ|ự|ý|ỳ|ỷ|ỹ|ỵ|í|ì|ỉ|ĩ|ị)(a|ă|â|ê|e|o|ô|ơ|u|ư|i|y)(a|ă|â|e|o|ô|ơ|u|ư|i|y|b|c|d|f|g|h|k|l|m|n|p|q|r|s|t|v|x)+/'; 

//---------------------------------------

// bắt lỗi cươí, tươí, cuôí---------------------------

$r4='/[bcdfghjklmnpqrstvxzđ]*(a|ă|â|ê|e|o|ô|ơ|u|ư|i|y)(a|ă|â|ê|e|o|ô|ơ|u|ư|i|y)(á|à|ả|ã|ạ|ắ|ằ|ẳ|ẵ|ặ|ấ|ầ|ẩ|ẫ|ậ|é|è|ẻ|ẽ|ẹ|ó|ò|ỏ|õ|ọ|ố|ồ|ổ|ỗ|ộ|ú|ù|ủ|ũ|ụ|ứ|ừ|ử|ữ|ự|ý|ỳ|ỷ|ỹ|ỵ|í|ì|ỉ|ĩ|ị)[bcdfghjklmnpqrstvxz]*/';

//----------------------------------------------------

// bắt lỗi ê không có dấu trong từ có dấu

$r5='/ê[a-z]*(á|à|ả|ã|ạ|ắ|ằ|ẳ|ẵ|ặ|ấ|ầ|ẩ|ẫ|ậ|é|è|ẻ|ẽ|ẹ|ó|ò|ỏ|õ|ọ|ố|ồ|ổ|ỗ|ộ|ú|ù|ủ|ũ|ụ|ứ|ừ|ử|ữ|ự|ý|ỳ|ỷ|ỹ|ỵ|í|ì|ỉ|ĩ|ị)/';

$r6='/(á|à|ả|ã|ạ|ắ|ằ|ẳ|ẵ|ặ|ấ|ầ|ẩ|ẫ|ậ|é|è|ẻ|ẽ|ẹ|ó|ò|ỏ|õ|ọ|ố|ồ|ổ|ỗ|ộ|ú|ù|ủ|ũ|ụ|ứ|ừ|ử|ữ|ự|ý|ỳ|ỷ|ỹ|ỵ|í|ì|ỉ|ĩ|ị)[a-z]*ê/';

//---------------------------------------

// bắt lỗi ơ không có dấu trong từ có dấu

$r7='/(á|à|ả|ã|ạ|ắ|ằ|ẳ|ẵ|ặ|ấ|ầ|ẩ|ẫ|ậ|é|è|ẻ|ẽ|ẹ|ó|ò|ỏ|õ|ọ|ố|ồ|ổ|ỗ|ộ|ú|ù|ủ|ũ|ụ|ứ|ừ|ử|ữ|ự|ý|ỳ|ỷ|ỹ|ỵ|í|ì|ỉ|ĩ|ị)[a-z]*ơ/';

$r8='/ơ[a-z]*(á|à|ả|ã|ạ|ắ|ằ|ẳ|ẵ|ặ|ấ|ầ|ẩ|ẫ|ậ|é|è|ẻ|ẽ|ẹ|ó|ò|ỏ|õ|ọ|ố|ồ|ổ|ỗ|ộ|ú|ù|ủ|ũ|ụ|ứ|ừ|ử|ữ|ự|ý|ỳ|ỷ|ỹ|ỵ|í|ì|ỉ|ĩ|ị)/';


 if (preg_match($r, $str) || preg_match($r2, $str) || preg_match($r3, $str) || preg_match($r4, $str) || preg_match($r5, $str) || preg_match($r6, $str) || preg_match($r7, $str) || preg_match($r8, $str)) {$bat_loi=1;}

return $bat_loi;
}

echo kiem_tra_dau_loi($str); // sẽ cho kết quả 1

Regex tuy trông hơi khó hiểu, nhưng mã sáng, dễ nhìn.

Cách thứ hai viết theo kiểu phân tích thông thường, mã trông khá rối, và cần chú thích nhiều mới hiểu được. Cách này vẫn là cách cơ bản hơn, vì không chỉ giải quyết được chuyện bắt lỗi, mà chính nó bổ sung thêm mã sẽ giúp sửa được các dấu thanh tiếng Việt đặt không đúng chuẩn.

Hàm bên dưới cũng yêu cầu nhiều điều kiện hơn trước khi xử lý:

  • Từ có tối đa 6 ký tự;
  • Từ chỉ có 1 dấu thanh, ví dụ từ hoạ, thủơ, hòang sẽ được bắt lỗi, còn từ như hòàng sẽ không làm gì cả, những từ như vậy cần tiền xử lý kiểm tra chính tả trước;
  • Khoảng cách giữa một nguyên âm có dấu và một nguyên âm không có dấu là không quá 2 ký tự, vì những từ như vậy cũng cần tiền xử lý chính tả trước, vì khả năng cao là nó sai chính tả nghiêm trọng;

Tóm lại trước khi dùng hàm này từ cần đúng chuẩn tương đối rồi, chỉ còn gặp lỗi về dấu thanh, và nó sẽ xử lý nốt.

function mang_hex_dau() {
     $hex_dau = array("cc80","cc81","cc83","cc89","cca3");

///////////////

function mang_codau() { // mảng nguyên âm có dấu, mã hóa phổ thông
    $cd=array("á","à","ả","ã","ạ","ắ","ằ","ẳ","ẵ","ặ","ấ","ầ","ẩ","ẫ","ậ","é","è","ẻ","ẽ","ẹ","ế","ề","ể","ễ","ệ","ó","ò","ỏ","õ","ọ","ố","ồ","ổ","ỗ","ộ","ờ","ớ","ở","ỡ","ợ","ú","ù","ủ","ũ","ụ","ứ","ừ","ử","ữ","ự","ý","ỳ","ỷ","ỹ","ỵ","í","ì","ỉ","ĩ","ị");
return $cd;
}


//////////////

function mang_khongdau() { // mảng nguyên âm không dấu
    $kd=array("a","â","e","ê","u","ư","o","ô","ơ","i","y");
return $kd;    
}

///////////////

// đảo dấu từ $x sang $y, tức là sau khi chuyển $y sẽ có dấu. Thông tin đầu vào là chữ cái được mã hóa theo kiểu phổ thông
function chdau($x,$y){ //đầu vào $x có dấu, $y không có dấu
    $dx=array("cc80","cc81","cc83","cc89","cca3"); // mã hóa dấu tiếng Việt, phải đưa vào trong hàm mới dùng được
    $hx=bin2hex(mahoa_itdung($x)); // chuyển sang mã hex để tìm dấu
    $hy=bin2hex(mahoa_itdung($y)); // chuyển sang mã hex để ghép dấu
    $i=0;$dau=array();
    foreach ($dx as $dxm) { // tách mảng dấu
                  $k='/'.$dxm.'/';
                  if (preg_match($k, $hx)) {
                      $dau[$i]=$dxm;
                      $hy=$hy.$dau[$i]; // thêm dấu cho $hy, nó vẫn đang ở dạng mã hex
                      $xd='/'.$dau[$i].'/';
                      $hx=preg_replace($xd,'',$hx); // khử dấu của $hx; nó vẫn đang ở dạng hex
                      $i++;
                  } // tìm ra dấu của $x
    }
    $dauma=chuyen_ma_hoa(hex2bin($hx)).'.'.chuyen_ma_hoa(hex2bin($hy)); // có dấu
    
return $dauma; // trả về mảng, biến đầu tiên là không dấu, biến thứ 2 là có dấu
}

function kiem_tra_chuyen_dau($str) {
$chuyen_dau = 0; // mặc định là không chuyển dấu, thỏa điều kiện mới chuyển sang 1   
// yêu cầu đầu vào là tiếng Việt không dấu đã được chuẩn hóa sang mã phổ biến, cần xử lý dính từ trước, có thể cần xử lý trước cả lỗi chính tả
$tvtd = mang_codau(); // các nguyên âm có dấu
$tkd = mang_khongdau(); // các nguyên âm không dấu
$dx = mang_hex_dau(); // mã hóa hex của dấu tiếng Việt

$tt = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY); // tách từ
$skt = count($tt)-1; // tìm số lượng ký tự, trừ đi 1 để tiện so với mảng bắt đầu từ 0

$tcd = array(); // mảng từ có dấu
$ml = 0; // đếm số lượng từ có dấu 

foreach ($tt as $ttm) { // $ttm lúc này là các ký tự của string
         foreach ($tvtd as $kt) { //$kt lúc này là các ký tự tiếng Việt có dấu
                                 if ($ttm==$kt) {$tcd[$ml]=$ttm; $ml++;}}} // tìm số lượng từ có dấu 
                                 
         $tg=0; // biến trung gian
         
if ($ml==1 && $skt < 6) { //có đúng một dấu thanh mới xử lý tiếp, giới hạn cả số ký tự để tránh lỗi dính từ, tối đa 6 ký tự

foreach ($tt as $ttm2) { 
    if ($ttm2==$tcd[0]) { // dấu luôn nằm trong phần tử đầu tiên của mảng có dấu
        $vtcd=$tg; // vị trí có dấu 
    } $tg++;
} // ra được vị trí của nguyên âm có dấu tính từ trái qua


// tạo ngoại lệ, những nguyên âm này nếu có dấu thì không cần chuyển
$ngle=($tt[$vtcd]!="ế" && $tt[$vtcd]!="ề" && $tt[$vtcd]!="ể" && $tt[$vtcd]!="ễ" && $tt[$vtcd]!="ệ" && $tt[$vtcd]!="ớ" && $tt[$vtcd]!="ờ" && $tt[$vtcd]!="ở" && $tt[$vtcd]!="ỡ" && $tt[$vtcd]!="ợ");


$mkd=array(); $slkd=0;
//đếm số lượng nguyên âm không dấu

foreach ($tt as $ttm3) {
                        foreach ($tkd as $kt3) {
                                                if ($ttm3==$kt3) 
                                                                {$mkd[$slkd]=$ttm3; $slkd++;}}} 
// mảng chỉ các vị trí nguyên âm không dấu


            if ($slkd==1) { // trường hợp có một nguyên âm không dấu
                            $tg4=0; // tìm vị trí của nguyên âm không dấu
                            foreach ($tt as $ttm4) {
                                                    if ($ttm4==$mkd[0]) {
                                                                         $vtkd=$tg4;} $tg4++;} // ra được vị trí của nguyên âm không dấu
                          $kc = abs($vtcd - $vtkd); // lấy giá trị khoảng cách từ, để phòng trường hợp dinh từ. Các nguyên âm cách nhau tối đa 1 từ, do vậy $kc không lớn hơn 2

                          if (($tt[$vtkd]=="ê" || $tt[$vtkd]=="ơ") && $kc < 3) {$chuyen_dau = 1;} // thỏa mãn điều kiện để chuyển dấu


                          else {

                                        // so sánh số lượng ký tự với vị trí của các nguyên âm
                                        if ($skt == $vtcd || $skt == $vtkd) {//tức là không có phụ âm đằng sau, vậy thì âm có dấu sẽ đứng đằng trước mới đúng chuẩn, nhưng có ngoại lệ với các âm có dấu ê và ơ và gi, qu
                                                        if ($vtcd > $vtkd && $kc < 3) { // tức là sai chuẩn có dấu đang đừng đằng sau
                                                                                    // tuy nhiên cần kiểm tra từ có dấu có phải là ê hoặc ơ không, nếu không thì mới phải đảo dấu
                                                                                    $gq=$tt[0].$tt[1]; // lấy 2 ký tự đầu tiên để kiểm tra nó có trùng gi và qu không
                                                                                                    if ($ngle && $gq != "gi" && $gq != "qu") { // thỏa mãn điều kiện chuyển dấu
                                                                                                    //bây giờ sẽ đảo dấu, dùng hàm cho tiện, vì việc này phải lặp lại
                                                                                                          $chuyen_dau = 1;
                                                                                                    }             
                                                        }
                                                        
                                                        if ($vtcd < $vtkd && $kc < 3) {
                                                                // dấu ở gi và qu, ví dụ gía qụa
                                                                 $gq=$tt[0].$tt[1]; // lấy 2 ký tự đầu tiên để kiểm tra n ó có trùng gi và qu không
                                                                                    if ($gq == "gí" || $gq == "gì" || $gq == "gỉ" || $gq == "gĩ" || $gq == "gị") {
                                                                                        $chuyen_dau=1;}
                                                                                        
                                                                                    if ($gq == "qú" || $gq == "qù" || $gq == "qủ" || $gq == "qũ" || $gq == "qụ") {
                                                                                        $chuyen_dau=1;}    
                                                        }
                                        }

                                        if ($skt > $vtcd && $skt > $vtkd && $kc < 3) { // tức là có phụ âm đằng sau // quy tắc thông thường sẽ là nguyên âm sau có dấu, tuy nhiên có ngoại lệ với ê và ơ có dấu
                                         // tức là nếu vị trí có dấu nhỏ hơn vị trí không dấu là cần chỉnh, ví dụ như hùynh
                                                       if (($vtcd < $vtkd) && $ngle)   {// cần phải vượt qua ngoại lệ ê ơ có dấu trong mọi trường hợp
                                                                                        //bây giờ sẽ đảo dấu, dùng hàm cho tiện, vì việc này phải lặp lại
                                                                                                    $chuyen_dau = 1;
                                                       }
                                        }
                          }
            }

 

            if ($slkd==2) { // trường hợp có hai nguyên âm không dấu
                            $tg5=0; // tìm vị trí của nguyên âm không dấu thứ nhất
                            $k=0; // tránh trùng nguyên âm, ví dụ giỏi, 2 âm i này sẽ có vị trí tối đa là 3
                            foreach ($tt as $ttm5) {
                                        if ($ttm5==$mkd[0] && $k<1) { //tách mảng không dấu
                                            $vtkd1=$tg5; // ra được vị trí của nguyên âm không dấu
                                            $k++;
                                        }
                                        if ($ttm5==$mkd[1]) {
                                            $vtkd2=$tg5;
                                        }
                                            $tg5++;                              
                            } // nguyên âm không dấu thứ 2
                                // với trường hợp có 3 nguyên thì dấu phải đặt ở nguyên âm thứ 2
                                // chúng ta sẽ phải chuyển trong trường hợp vị trí có dấu ở vị trí 1 hoặc 3 như tứơi hoặc tươí
                                // ngoại lệ với ê và ơ có dấu
                            $kc = abs($vtcd - $vtkd2);
                            $uut=0;

                            if (($tt[$vtkd1]=="ê" || $tt[$vtkd1]=="ơ") && $kc < 3) {$chuyen_dau=1; $uut=1;}

                            if (($tt[$vtkd2]=="ê" || $tt[$vtkd2]=="ơ") && $kc < 3) {$chuyen_dau=1; $uut=1;}  

                            if ($uut!=1) {

                                            if ((($vtcd>$vtkd1 && $vtcd>$vtkd2)) && $kc < 3) { //có dấu đang ở vị trí 3
                                            // cần phải vượt qua ngoại lệ với ê và ơ có dấu
                                                    if ($ngle) { 
                                                    //bây giờ sẽ đảo dấu, dùng hàm cho tiện, vì việc này phải lặp lại
                                                                $chuyen_dau = 1;
                                                    }}


                                            if ((($vtcd<$vtkd1 && $vtcd<$vtkd2)) && $kc < 3) { //có dấu đang ở vị trí 1
                                                                            // cần phải vượt qua ngoại lệ với ê và ơ có dấu
                                                                            if ($ngle) { 
                                                                                        //bây giờ sẽ đảo dấu, dùng hàm cho tiện, vì việc này phải lặp lại
                                                                                        $chuyen_dau = 1;
                                                                             }                         
                                             }

                            }

            }
}
return $chuyen_dau; // để biết có nên chuyển dấu hay không
}

Leave a Comment