close

regex.png

【PHP】 UTF-8 multi byte in preg (regex) function

問題:

  PHP 為了支援常見編碼 (如 UTF-8) 上 multi byte 的 characters,提供了 mb_* 家族類 function 專門處裡這些 multi byte characters。

 

  但並不是每一個字串相關 function 都有相對應的 mb_* function,今天要分享的就是 PHP 在 preg_* 家族類 function,也就是專門處裡 regex (正規表達式) 相關的 function 預設也是不支援 UTF-8 multi byte 的。

 

$string = "【武器】長劍";
preg_match('/^【([^】]*)】/', $string, $match);
var_dump($match);
 
$string = "【技能】雙劍斬擊";
preg_match('/^【([^】]*)】/', $string, $match);
var_dump($match);

  舉個例子,假設某 RPG 遊戲中的專有名詞前方都會有一組 "【】" 符號內表示該名詞的所屬分類,而我們想做的就是使用 preg_match 提取該分類名稱。

 

array(2) {
    [0] => string(12) "【武器】"
    [1] => string(6) "武器"
}
 
array(0) {
}

  看起來結果與預期不同,"【技能】雙劍斬擊" 這一筆資料無法被 preg_match 提取成功。接下來會解釋這個結果是怎麼運算出來的。

 

發生原因:

  解答前要先講講 UTF-8 編碼方式,在 【PHP】trim not support multi byte & UTF-8 encode 文章中有講解到 UTF-8 的編碼方式,在這邊只放重點,有興趣的可以看該篇文章較詳細的說明。

 

  UTF-8 本身是編碼長度是不固定的,其解碼方式如下:

  • 第一個 byte 的開頭是 0,代表這是一個 1 byte 長度的字元。
  • 第一個 byte 的開頭是連續 N 個 1,代表這是一個 N byte 長度的字元。
  • 同時多 byte 字元,第一個以外的 byte 都會使用 10 作為開頭。
  • 剩下的 bit 就照順序組合後對應 Unicode 萬國碼的編碼。

  但 preg_* 家族類 function 預設是不支援 UTF-8 多 byte 的,所以剛剛範例中的中文字通通 preg_match 被當成各三個 character 在處理,其中 "技" 與 "】" 的 UTF-8 為:

 

var_dump(bin2hex('技')); // string(6) "e68a80"
var_dump(bin2hex('】')); // string(6) "e38091"

 

regex.png

  所以其實我們剛剛執行的 regex 規則是:

 

^【

【開頭

([^】]*)

不包含 e3 80 91 任意 character

e3 80 91 三個連續的 character 結尾

  而 e6 8a 80 (技) 當中就包含 80,不符合規則,自然就不會被 preg_match 提取出來。當然其他的中文字與特殊符號也都被當成對應的多個 character 在處理,只是剛好都符合規則所以看似正常運作而已。

 

解答:

  要解決這個問題只要使用 preg_* 家族類 function Pattern Modifiers 中的 PCRE_UTF8 即可支援 UTF-8。

 

u (PCRE_UTF8)

This modifier turns on additional functionality of PCRE that is incompatible with Perl. Pattern and subject strings are treated as UTF-8. An invalid subject will cause the preg_* function to match nothing; an invalid pattern will trigger an error of level E_WARNING. Five and six octet UTF-8 sequences are regarded as invalid.

來源:PHP: Possible modifiers in regex patterns - Manual

 

$string = "【技能】雙劍斬擊";
preg_match('/^【([^】]*)】/u', $string, $match);
var_dump($match);
 
/*
array(2) {
    [0] => string(12) "【技能】"
    [1] => string(6) "技能"
}
*/

  在 regex 後方加上 u modifier 就可以順利將 "【技能】雙劍斬擊" 中的 "技能" 分類提取出來啦。

 

參考資料:

[1] PHP: Possible modifiers in regex patterns - Manual

https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php

[2] PHP: bin2hex - Manual

https://www.php.net/manual/en/function.bin2hex.php

[3] PHP: Multibyte String - Manual

https://www.php.net/manual/en/book.mbstring.php

 

arrow
arrow
    創作者介紹
    創作者 迷宮兔 的頭像
    迷宮兔

    兔窩

    迷宮兔 發表在 痞客邦 留言(0) 人氣()