101. 音译转化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字符码查找替换即可,在理解代码之前需要先明白一些编码知识。

 

UnicodeUTF-8编码:

Unicode码称为统一码,或万国码,是一种包含世界各国语言字符的编码,目前还在不断发展中以包括更多字符,UTF-8是为解决Unicode码储存浪费问题而生的再次编码方案,表示一个Unicode字符的UTF-8编码是变长的,目前代表Unicode字符的UTF-8编码最多4字节(每字节有8比特),具体有多少字节可由UTF-8编码最前面几比特推断出来,第一比特如果为0则仅一字节,如果为110111011110则分别代表有234字节,规则有以下两点:

一、对于单字节的符号,字节的第一位设为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,十六进制表示为:5ba2Unicode编码正是其十六进制表示,记为“\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=%......&*()--+[()] {}()

 

 

本书共133小节:

评论 (写第一个评论)