雲客Drupal源碼分析之音譯轉化Transliteration

音譯轉化Transliteration服務用於依據發音將Unicode字符串轉化爲US-ASCII字符串,這和翻譯是不同的概念,對於中國人來說最直觀的理解就是將中文文字轉變爲拼音,Unicode涵蓋世界所有語言的字符,因此該服務可轉換所有的語言,而不僅僅用於中文;在drupal中通常用於依據用戶輸入產生識別id,如在後臺定義字段操作中,輸入中文的標籤時,系統用該服務自動產生機器名。

服務定義及使用示例:
服務定義如下:

  transliteration:
    class: Drupal\Core\Transliteration\PhpTransliteration
    arguments: [null, '@module_handler']

第一個參數是數據目錄,如果爲NULL將使用類文件所在目錄下的“data”目錄,默認如下:
 core\lib\Drupal\Component\Transliteration\data
服務獲取方法:\Drupal::transliteration()

使用示例:
在控制器中運行以下代碼:

    $str="我是雲客,很高興認識您。";
    $lang=\Drupal\Core\Language\LanguageInterface::LANGCODE_DEFAULT;
    echo \Drupal::transliteration()->transliterate($str, $lang, '_');

將輸出:

“woshiyunke,hengaoxingrenshinin. ”

如果想進一步得到變量名,可以這樣處理:

$transliterated = \Drupal\Component\Utility\Unicode::strtolower($transliterated);
$transliterated = preg_replace('@[^a-z0-9_.]+@', '', $transliterated);

實現原理概述:
你可能會對此感到非常好奇,但實際上很簡單,系統附帶了一份Unicode編碼與音譯字符的對應文件,用戶也可以依據語言自定義該映射數據,在轉化時按Unicode字符碼查找替換即可,在理解代碼之前需要先明白一些編碼知識。

Unicode與UTF-8編碼:
Unicode碼稱爲統一碼,或萬國碼,是一種包含世界各國語言字符的編碼,目前還在不斷髮展中以包括更多字符,UTF-8是爲解決Unicode碼儲存浪費問題而生的再次編碼方案,表示一個Unicode字符的UTF-8編碼是變長的,目前代表Unicode字符的UTF-8編碼最多4字節(每字節有8比特),具體有多少字節可由UTF-8編碼最前面幾比特推斷出來,第一比特如果爲0則僅一字節,如果爲110、1110、11110則分別代表有2、3、4字節,規則有以下兩點:

一、對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的。
二、對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一律設爲10。剩下的沒有提及的二進制位,全部爲這個符號的unicode碼。

換句話說UTF-8編碼字節數和對應的形式如下:
1字節形式:0xxxxxxx
2字節形式:110xxxxx 10xxxxxx
3字節形式:1110xxxx 10xxxxxx 10xxxxxx
4字節形式:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
如果不是這樣的形式,則不是有效的UTF-8字符,UTF-8編碼可表示所有的Unicode編碼字符,將以上這些形式中的X按順序提取出來形成的二進制串就是Unicode碼的整數值,本文將其稱爲Unicode字符碼,她是本篇的重點,系統據此查找並替換音譯字符。

爲便於讀者理解,雲客在這裏提供以下兩個輔助程序:
一個UTF-8編碼字符可以通過以下函數顯示其內部的二進制形式表示:

function echoUTF8($character)
{
    $count = 0;
    for ($i = 3; $i >= 0; $i--) {
        if (isset($character[$i])) {
            $count = $i;
            break;
        }
    }
    $s = '';
    for ($i = 0; $i <= $count; $i++) {
        $s .= str_pad(base_convert(ord($character[$i]), 10, 2), 8, '0', STR_PAD_LEFT) . " ";
    }
    echo $s;
}

如echoUTF8 ("客");,將輸出漢字“客”的utf8內部表示:
“11100101 10101110 10100010”,
按照上述轉Unicode的方法(提取模板中的x)提取二進制串將是:
“0101 101110 100010”
它的十進制表示爲:23458,十六進制表示爲:5ba2,Unicode編碼正是其十六進制表示,記爲“\u5ba2”

一個UTF-8編碼字符可以通過以下程序得到Unicode字符碼的十六進制表示:

function echoUnicode($character)
{
    $first_byte = ord($character[0]);
    $code = -1;
    if (($first_byte & 0x80) == 0) {
        $code = $first_byte;
    }
    if (($first_byte & 0xe0) == 0xc0) {
        $code = (($first_byte & 0x1f) << 6) + (ord($character[1]) & 0x3f);
    }
    if (($first_byte & 0xf0) == 0xe0) {
        $code = (($first_byte & 0x0f) << 12) + ((ord($character[1]) & 0x3f) << 6) + (ord($character[2]) & 0x3f);
    }
    if (($first_byte & 0xf8) == 0xf0) {
        $code = (($first_byte & 0x07) << 18) + ((ord($character[1]) & 0x3f) << 12) + ((ord($character[2]) & 0x3f) << 6) + (ord($character[3]) & 0x3f);
    }
    if ($code == -1) {
        echo "the character is not legal.";
        return;
    }
    echo $s = '\u' . str_pad(base_convert($code, 10, 16), 4, '0', STR_PAD_LEFT);
}

調用echoUnicode("客");將輸出:“\u5ba2”

源碼解釋:
在知道了原理後我們來看一看程序,代碼以drupal組件的方式提供(意味着可獨立用於其他項目),核心繼承添加了模塊修改功能,各方法介紹如下:
public function transliterate($string, $langcode = 'en', $unknown_character = '?', $max_length = NULL);
將字符串音譯轉化爲US-ASCII字符串,參數含義如下:
$string:要被轉化的字符串,需傳入UTF-8編碼的字符串,否則視爲無效以未知字符代替
$langcode:被轉化的字符串所屬的語言的語言代碼,默認爲en(英語),以運用語言特定的覆寫
$unknown_character :當找不到轉化等價物時的代替字符串,默認爲'?'
$max_length:轉化後的字符串最大長度限制,默認爲 NULL,代表不限制,在截取時以原字符作爲單位,不會將一個字符劈開,舉個例子:“雲客”轉化後是“yunke”,如果該參數被設置爲4,結果將是“yun”,而不是“yunk”,因爲“客”作爲一個整體,如果截取會使其被劈開,此時將整個捨去
該方法通過正則表達式分隔函數preg_split逐個字符處理,'//u'表示按unicode(utf-8)匹配(主要針對多字節比如漢字),如果字符不是合法的UTF-8編碼的unicode字符,將用傳入的未知字符作爲轉化結果

protected static function ordUTF8($character)
返回一個UTF-8編碼的Unicode字符的字符編碼十進制整數表示,類似ord函數,但該方法是針對utf8的 ,如果傳入的不是UTF-8編碼字符將返回-1,該返回值也就是前一節所指的Unicode字符碼,可將其當做一個十進制整數,比如“雲客”的“客”傳入該方法將返回十進制整數:23458,該返回值轉化爲十六進制即是傳入字符的Unicode碼錶示,代碼如下:

$int =23458;
$s = '\u' . str_pad(base_convert($int, 10, 16), 4, '0', STR_PAD_LEFT);

此時$s的值爲“\u5ba2”,正是漢字“客”的Unicode碼錶示
該方法使用了位操作,提示:用連續的1進行與(&)操作等價於針對這些位的截取操作,爲閱讀方便,這裏將用到的十六進制對應的二進制列出如下:

0x80 : 10000000
0xe0 : 11100000
0xc0 : 11000000
0x1f : 00011111
0x3f : 00111111
0xf0 : 11110000
0xe0 : 11100000
0x0f : 00001111
0xf8 : 11111000
0x07 : 00000111

protected function replace($code, $langcode, $unknown_character)
依據十進制的Unicode字符碼返回音譯替換字符,替換字符可以包含多個US-ASCII字符,如果Unicode字符碼在ASCII集以內,將直接通過函數chr轉換返回,如果不在則查詢映射文件,映射文件有兩類:默認通用映射和語言特定的覆寫映射,後者優先級更高

protected function readLanguageOverrides($langcode)
加載語言覆寫映射文件,文件名是將語言代碼中除字母和連字符“-”以外的字符去掉的結果,php文件類型,文件位置默認在core\lib\Drupal\Component\Transliteration\data中(可以通過構造函數改變默認位置),文件內容僅需聲明一個php變量$overrides,如德語(語言代碼爲de)的內容爲:

$overrides['de'] = [
  0xC4 => 'Ae',
  0xD6 => 'Oe',
  0xDC => 'Ue',
  0xE4 => 'ae',
  0xF6 => 'oe',
  0xFC => 'ue',
];

鍵名爲語言代碼,鍵值爲一個數組,稱爲覆寫數據數組,其鍵名爲十六進制表示的Unicode字符碼,也就是前文ordUTF8方法返回的值,注意該鍵名不要加引號,否則就變成字符串而不是整數了,鍵值爲音譯替換字符。
該文件在方法內加載,因此加載的變量是局部變量,如果沒有聲明$overrides將構造一個空數組,加載的覆寫數據數組被保存在屬性$this->languageOverrides[$langcode]中
Drupal覆寫了該方法,使得模塊可以通過修改鉤子修改覆寫映射,鉤子名如下:
  transliteration_overrides
默認沒有模塊實現該修改鉤子,修改鉤子函數如下:
  hook_transliteration_overrides_alter(&$overrides, $langcode);
參數$overrides爲覆寫數據數組,鍵名爲十六進制表示的Unicode字符碼,見上文,如果不存在覆寫數據,也會派發該鉤子,此時該參數爲一個空數組,模塊可以添加覆寫數據。參數$langcode爲語言代碼

protected function lookupReplacement($code, $unknown_character = '?')
依據Unicode字符碼返回默認的音譯映射字符串,這是通過查詢預先準備的默認映射數據實現的,映射數據儲存方式如下:
將Unicode字符碼的低8位去除,剩下的高位轉化爲十六進制,不足兩位時在左端補0,加“x”作爲前綴,以此方式得出的字符串作爲文件名,php類型,文件位置和語言覆寫數據相同,文件內容僅聲明一個變量$base,其值爲一個數組,鍵名爲Unicode字符碼低8位轉化的整數,以十六進制方式表示,在默認數據中可以看到許多鍵名被省略,這是因爲php會按順序加一形成鍵名;鍵值爲音譯替換字符,可以有多個,這裏仍然以“客”作爲列子,其Unicode字符碼爲23458,十六進制表示爲:5ba2,低八位是“a2”,其他高位爲“5b”,那麼文件名就是“x5b.php”,打開這個文件,在“0xA0”位置是chong,向右兩個元素就是“0xa2”,其鍵值正是“ke”。

public function removeDiacritics($string)
移除變音符號,變音符號(diacritics 或accents)是標明一個詞如何發音的符號,在法語和西班牙語等語言中非常常見,在英語中不常見,對於漢語來說更是陌生,詳見:
https://en.wikipedia.org/wiki/Diacritic
該方法實現比較簡單,不多講


補充說明:
1、該服務不會考慮漢語拼音多音字問題,如“行走”、“行業”中的“行”都會被轉化爲“xing”,正確轉化拼音是一個很複雜的問題,需要考慮前後上下文,需要多音字相關的額外數據
2、該服務僅處理utf-8編碼的字符串,不能直接處理GBK等編碼,如有需要須先通過php函數轉化字符串爲utf-8編碼
3、該服務的實現代碼有部分來自MediaWiki項目的UtfNormal類,地址:
http://www.mediawiki.org/
4、如果轉化結果需要當做php變量使用,需要做進一步處理,去除標點符號等特殊字符,以下中文標點符號和對應的轉化結果如下:

原中文符號: ,。、;‘”:?!@#¥%……&*()——+【】{}()
轉化後符號: ,.,;'":?!@#Y=%......&*()--+[()] {}()

 

我是雲客,【雲遊天下,做客四方】,聯繫方式見主頁,歡迎轉載,但須註明出處

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章