Tommy 碎碎念

Tommy Wu's blog

« 上一篇 | 下一篇 »

phpBB 由 BIG5 轉換到 UTF-8
post by tommy @ 09 四月, 2006 10:37
以往, 當你的網站上來的使用者, 都來自台灣時, 自然大家都使用這兒最普遍的中文編碼 BIG5, 所以網頁用的文字以及資料庫的內容, 自然也就是使用 BIG5 碼來儲存.

可是, BIG5 碼只考慮到正體中文的使用, 並未考慮到其他文字的使用. 所以, 當有人使用不同編碼時, 在 BIG5 的系統上頭, 看起來就變成亂碼一樣. 為了解決這個問題, 所以有了 Unicode 的出現, 而 UTF-8 是目前在多數系統上頭都支援的 Unicode 方案. 有了 UTF-8 之後, 我們便可以在同一篇文章中, 同時打入各國的文字, 而不會變成亂碼.

目前網路上找一下, 可以發現有許多文章在說明如何轉換. 多數都是先把資料庫的內容 dump 出來, 然後使用 iconv 來轉換為 UTF-8, 然後再存入. 而 ols3 有寫了一個直接讀出資料轉換後再存入另一個資料庫的程式. 似乎只有上頭兩種方式.

第一種方式有些小問題, 使用 iconv 轉碼時, 如果同一篇文章只能由某一種內碼轉換到另一種內碼, 所以如果有些內容是同時存在多種內碼時, 就會發生錯誤. 而且, 似乎某些中文字的後頭會多出一個 \ 出來. 這些都要另外去修改. 不過對於一個很大的資料庫來說, 這個修改就變成一個很頭大的動作了.

第二種方式, 我下載後並沒有試過, 據說可以解決多出那個 \ 的問題. 但是多種內碼存在同一篇文章的情形, 並沒有說明是否有特別處理. 而且.... 那是一個 linux 的 ELF 格式的執行檔. 對我來說, 不知道裡頭到底做了那些動作, 所以不想直接拿來使用.

所以, 就自己寫了一個 script, 做出類似第二種方式的處理.  直接把資料庫的內容轉成一個 SQL 指令的檔案. 自己在裡頭做轉碼的動作. 這個後頭再來說明.

先說比較容易處理的部份. 有關 apache 與 phpbb 的 UTF-8 支援部份. 首先, 我們先使用 ols3 所寫的另一個 b2u 的 php script 來轉換 phpbb 的檔案為 UTF-8.

不過這個 script 有點小 bug, 它是依據檔名的後四個字元來判斷是否做轉碼的動作, 但是在 ".tpl" 與 ".css" 的部份, 卻不小心弄成了 "tpl" 與 "css", 所以實際上並沒有處理到 .tpl 與 .css 的檔案. 修改後的 script 如下:

#!/usr/bin/php -q
<?php
dir_work($_SERVER["argv"][1]);

function dir_work($toget_dir=""){
if(substr($toget_dir,-1)=="/"){
$toget_dir=substr($toget_dir,0,-1);
}
$d=explode("/",$toget_dir);
$n=sizeof($d);
$k=$n-1;

$utf8_dir=$d[$k]."_utf8";
echo $utf8_dir."\n";
$d[$k]=$utf8_dir;
$new_dir=implode("/",$d);

dir_encode($toget_dir,$new_dir);
return;
}


function dir_encode($toget_dir="",$new_dir=""){
if(!is_dir($new_dir)){
mkdir($new_dir);
}
if ($dir = @opendir($toget_dir)) {
while (($file = readdir($dir)) !== false) {
if($file=="." or $file==".."){
continue;
}elseif(is_dir($toget_dir."/".$file)){
dir_encode($toget_dir."/".$file,$new_dir."/".$file);
}else{
$type=substr($file,-4);
$ok_array=array(".txt",".php",".sql",".htm","html",".tpl",".css");
if(in_array($type,$ok_array)){
$exec="piconv -f BIG5 -t UTF-8 ".$toget_dir."/".$file." > ".$new_dir."/".$file;
echo $exec."\n";
exec($exec);
}else{
copy($toget_dir."/".$file,$new_dir."/".$file);
}
}
}
closedir($dir);
}
return;
}
?>

我們只要使用這個 script, 就可以把原本的 phpbb 所在的目錄, 複製一份到 _utf8 結尾的目錄, 而裡頭的檔案, 都被轉換為 UTF-8 編碼了.

接著修改 lang_main.php 中的

$lang['ENCODING'] = 'big5'; 

改成

$lang['ENCODING'] = 'utf-8'; 

然後修改 language 裡頭 email 下的目錄, 把所有出現 big5 的地方, 都改成 utf-8.

接著修改 include/emailer.php 的內容, 在 // Send message 之後加上

                $subject = '=?'.$this->encoding.'?B?'.base64_encode($this->subject).'?=';
                $this->subject = $subject;  

把 email 的標題, 使用 base64 編碼來處理, 以避免收到信的時候, 標題的處理可能會出現的亂碼.

然後修改 db/mysql.php 與 db/mysql4.php (看你用那一個), 在 mysql_connect 之後, 傳回 id 之前, 加上 (如果用 mysql 4.0.x 或之前的版本, 這個動作加不加都一樣)

$this->sql_query("SET NAMES 'utf8'"); 

如果有使用 adv_top5.php 這個 MOD, 也要修改一下這個檔案內的 cutStr(), 改用 mb_strlen() 來抓 UTF-8 字串長度:

function cutStr($str) {

global $MAX_STR_LEN;

// check lenght use mb_strlen() for UTF-8
$len = mb_strlen($str, "UTF-8");
if ($len > $MAX_STR_LEN) {
$str = mb_strcut($str, 0, $MAX_STR_LEN, "UTF-8")."...";
}
/*
$str = (strlen($str) > $MAX_STR_LEN) ? (substr($str, 0, $MAX_STR_LEN - 1) . "...") : $str;
*/
return $str;
}

然後, 修改 apache 的設定, 在新的目錄的位置加上

<Directory /var/www/phpbb_utf8>
AllowOverride FileInfo
</Directory>  

至少要有 FileInfo 的權限. 然後在該目錄加上 .htaccess 檔案, 內容是

AddDefaultCharset UTF-8 

這樣子可以使這個目錄的資料, 在 apache 送出的 header 中, 表示是使用 UTF-8 編碼的.

這樣子的處理之後, 我們就會有一個使用 UTF-8 的環境了, 剩下來的就是資料庫裡頭的內容了.

首先, 我們先使用下頭的指令, 把目前用的資料庫的 schema 抓出來

mysqldump -d -p -u dbuser dbname > phpbb_schema.sql 

產生的 phpbb_schema.sql 檔案, 就是目前我們使用的資料庫的 schema, 接著, 修改這個檔案的內容, 在 MyISAM 之後, 加上 DEFAULT CHARSET=utf8, 用來表示資料是存放為 UTF-8 的格式. (如果你使用的是 mysql 4.0.x 或之前的版本, 系統並未真的支援 UTF-8 存放於資料庫中, 一般來說, 仍是用 latin1 編碼來儲存 UTF-8 的文字, 則上一個改 CHARSET 的動作可能沒有作用)

接著修改裡頭某些 char 欄位的長度, 因為 UTF-8 並非固定長度, 所以不修改的話, 並無法存入原本你想像的那麼多的字元, 至於想改多大, 你就自行決定吧 (在 MySQL 4.1 或之後有直接支援 UTF-8 字元的版本, 並不需要做這個修改, 依據 MySQL 的文件指出, 對於 UTF-8 的欄位, varchar 或 char 都是用 3 個 bytes 來存一個 char) . 另外, 部份欄位使用 text 格式, 在 mysql 的限制中, 這個格式最大為 65535 bytes, 如果你希望同一篇文章的長度超過這個限制, 可以改用 mediumtext 格式.

修改後,  建立一個新的資料庫叫 dbname_utf8, 然後建立這些 table

mysql -f -p -u dbuser dbname_utf8 < phpbb_schema.sql 

這樣子就會建立一個新的, 使用 UTF-8 的資料庫了. 接著把 UTF-8 環境下的 phpbb 的 config.php 中資料庫的設定指到新的這個資料庫.

目前所有的動作, 都是可以先行處理的, 並不需要停止系統的服務來處理. 而接下來的資料庫內容轉碼的動作, 為了避免資料轉換過程中, 有新的資料產生, 所以需要停止 phpbb 的運作才能進行.

我們先使用下面的 script, 執行這個 script 後, 應該會產生一個 utf8.sql 的檔案:

<?php

set_time_limit(0);

$phpname = 'phpbb_';
$dbuser = 'dbuser';
$dbpass = 'dbpass';
$dbhost = 'localhost';
$dbname = 'dbname';

$encoding_cnt = 0;
$encoding[$encoding_cnt++] = 'BIG5';
$encoding[$encoding_cnt++] = 'GB2312';
$encoding[$encoding_cnt++] = 'GBK';
$encoding[$encoding_cnt++] = 'SHIFT_JIS';
$encoding[$encoding_cnt++] = 'GB18030';

$conn = mysql_connect($dbhost, $dbuser, $dbpass);
if (!$conn) {
echo "Can't connect to mysql at $dbhost!";
exit;
}

if (!mysql_select_db($dbname, $conn)) {
echo "Can't use database $dbname!";
mysql_close($conn);
exit;
}

//mysql_query("SET NAMES 'big5'", $conn);
if (!($result = mysql_list_tables($dbname, $conn))) {
echo "Can't get table for $dbname!";
mysql_close($conn);
exit;
}
while ($row = mysql_fetch_row($result))
$tables[] = $row[0];
mysql_free_result($result);

$fp = fopen("utf8.sql", "wt");
if ($fp == 0) {
echo "Can't open utf8.sql!";
mysql_close($conn);
exit;
}

fputs($fp, "SET NAMES 'utf8';\n");

$cnt = 0;
foreach ($tables as $tblname) {
$cnt++;
if ($tblname == $phpname.'search_wordlist' ||
$tblname == $phpname.'search_results' ||
$tblname == $phpname.'search_wordmatch') {
echo "skip for $tblname<br>\n";
continue;
}
fputs($fp, "delete from `$tblname`;\n");
echo "Table: $tblname<br>\n";
echo "<blockquote>\n";
$sql = "select * from `$tblname`";
$result = mysql_query($sql, $conn);
if (!$result) {
echo "Query failed: $sql\n";
}
else {
$fields = array();
$num = mysql_num_fields($result);
for ($i = 0; $i < $num; $i++) {
$meta = mysql_fetch_field($result);
if (!$meta) {
echo "No meta information!\n";
}
else {
$name = $meta->name;
$type = $meta->type;
echo "'$name' => '$type'<br>\n";
$fields[$i]['name'] = $name;
$fields[$i]['type'] = $type;
}
}
$row_cnt = 0;
$err = 0;
while ($row = mysql_fetch_row($result)) {
$row_cnt++;
$field_list = '';
$value_list = '';
$id = $row[0];
for ($i = 0; $i < $num; $i++) {
$type = $fields[$i]['type'];
$value = $row[$i];
if ($value == NULL) continue;
if ($field_list == '') {
$field_list = '`'.$fields[$i]['name'].'`';
}
else {
$field_list .= ',`'.$fields[$i]['name'].'`';
}
if ($type == 'string' || $type == 'blob') {
$lines = explode("\n", $value);
$n = count($lines);
$uvalue = '';
for ($ln = 0; $ln < $n; $ln++) {
$xline = $lines[$ln];
$ok = 0;
for ($x = 0; $x < $encoding_cnt; $x++) {
$code = $encoding[$x];
// I think we should remove //TRANSLIT here, return error to change another encoding
//$xvalue = iconv($code, 'UTF-8//TRANSLIT', $xline);
$xvalue = iconv($code, 'UTF-8', $xline);
if ($xvalue === false) {
//echo "$id, Can't convert $xline from $code<br>\n";
}
else {
//echo "convert '$xline' to '$xvalue' for $code<br>\n";
$ok = 1;
break;
}
}
if ($ok == 0) {
$xvalue = $xline;
//echo "failed for all encoding<br>\n";
}
if ($ln == 0)
$uvalue = $xvalue;
else
$uvalue .= "\n".$xvalue;
}
if (preg_match("/&#\d{4,7};{0,1}/", $uvalue)) {
$x = preg_replace_callback(
"|(&#)(\d{4,7})(;{0,1})|",
"num2utf",
$uvalue);
//echo "convert '$uvalue' to '$x'<br>\n";
$uvalue = $x;
}
$str = mysql_escape_string($uvalue);
if ($value_list == '') {
$value_list = "'$str'";
}
else {
$value_list .= ",'$str'";
}
}
else {
if ($value_list == '') {
$value_list = $value;
}
else {
$value_list .= ",$value";
}
}
}
$sql = "insert into $tblname ($field_list) values ($value_list)";
fputs($fp, $sql);
fputs($fp, ";\n");
}
}
echo "</blockquote>\n";
}

fclose($fp);
mysql_close($conn);

function code2utf($num)
{
//Returns the utf string corresponding to the unicode value
//courtesy - romans@void.lv
if ($num<128)
return chr($num);
if ($num<2048)
return chr(($num>>6)+192).chr(($num&63)+128);
if ($num<65536)
return chr(($num>>12)+224).chr((($num>>6)&63)+128).chr(($num&63)+128);
if ($num<2097152)
return chr(($num>>18)+240).chr((($num>>12)&63)+128).chr((($num>>6)&63)+128). chr(($num&63)+128);
return '';
}

// the callback function
function num2utf($matches) {
// as usual: $matches[0] is the complete match
// $matches[1] the match for the first subpattern
// enclosed in '(...)' and so on
$code = $matches[2];
return code2utf($code);
}

?>

然後把這個檔案的內容塞入新的資料庫

mysql -f -p -u dbuser dbname_utf8 < utf8.sql 

等資料轉好後, 把 apache 中指向 phpbb 的設定, 切換到新的目錄, 這樣子系統就變成 UTF-8 了. 你可以用 broswer 連上去看看, 應該是正確的 UTF-8 編碼了.

最後, 說明一下那個轉碼 script 的動作:

  1. 先抓出所有的 table 名稱.
  2. 由於 search_wordlist, search_results, search_wordmatch 三個 table 只是搜尋的 cache, 可以清除而不影響系統運作, 所以不轉換這三個 table.
  3. 在 utf8.sql 一開頭, 先執行 SET NAMES 'utf8'; 以確定使用 UTF-8.
  4. 在每個 table 轉碼前先 delete 該 table 的資料.
  5. 抓取 table 的欄位名與資料格式. 我們只針對 string 與 blob 格式進行轉碼. (也許 blob 不應該也不需要轉碼, 在 phpBB 中沒有這類的格式, 如果你的系統有這類格式的欄位, 請先轉換測試看看是否有此需求)
  6. 系統為避免同一篇文章內出現不同的編碼, 造成 iconv 轉換到該編碼後就停止, 所以每次只轉一行. 如此, 應該只有同一行出現不同編碼的情形才會轉換失敗.
  7. 把原本 &#nnnn; 之類的字元, 也轉換成 UTF-8 的字元.  
  8. 產生 insert 的 SQL 指令存入 utf8.sql.

這個轉碼與存入資料庫的動作, 並不會太久, 依據你的資料庫大小而定, 以酷學園為例, 有 12000 的使用者與 160000 的文章, 轉換與存入的動作, 在 5-10 分鐘就可以完成.


2006/07/20: 經 Darkhero 兄提醒, 在 MySQL 5.0 (可能是 4.1 之後的版本) 中, 如果要轉換, 抓出來的型態中, date, datetime 兩個型態也是要比照字串辦理. 上述程式的 if ($type == 'string' || $type == 'blob') 要改成 if ($type == 'string' || $type == 'blob' || $type == 'date' || $type == 'datetime') 才可以.

 

Del.icio.us Furl HEMiDEMi Technorati MyShare
commons icon [1] [ 回覆 ]

請教一下,有些big5字元無法mapping到utf8,
這種情況下會是如何處理呢?

commons icon [2] [ 回覆 ]

我沒碰過在 big5 裡頭有, 但 utf-8 裡頭沒有的字. (可能性不高吧, 除非某些後來才跑出來的特殊字, 否則 utf-8 的集合比 big5 大上許多, 照說都能 mapping)
如果有, 可能就像其他無法辨識的字一樣吧. 那一行字可能轉到那個字就轉不下去了.

commons icon [3] 請問這樣轉換會不會有許功蓋的問題呢 [ 回覆 ]

請問這樣轉換會不會有許功蓋的問題呢,因為我的資料庫跟酷學園的大小差不多,預計會有許多許工蓋在字尾,不知道這樣會不會導致轉換失敗呢?

commons icon [4] [ 回覆 ]

應該不會. 這個作法與你在實際使用 phpbb 時看到的資料應該會一樣. 也就是你如果在 phpbb 看的內容是正確的, 轉出來就會是正確的.

commons icon [5] 欲輸出schema的問題 [ 回覆 ]

請問當我用以下指令輸出schema時:
mysql -d -p -u dbuser dbname > phpbb_schema.sql
mysql卻告訴我沒有 -d 這個選項?
那我該怎麼輸出schema啊?
不好意思,我是初學者!

commons icon [6] [ 回覆 ]

Sorry, 應該是用 mysqldump 才對.
mysqldump -d -p -u dbuser dbname > phpbb_schema.sql

commons icon [7] [ 回覆 ]

感謝您提供的方案,讓我成功轉換到 UTF-8 了。 ^^/

關於 adv_top5.php ,我想補充一點就是,原來
$MAX_STR_LEN = 60;
這裡可以改成
$MAX_STR_LEN = 30;
這樣才會真的把太長的標題截短。

commons icon [8] [ 回覆 ]

請問如果在同一行內包括不同編碼, 該如何處理?

例如:

1. 銀魂 = ok

2. 銀魂(ぎんたま) = 日文部份被移除.

commons icon [9] [ 回覆 ]

可以考慮用一個 loop 或遞迴來處理. 把轉成功的字串再轉回原本的編碼, 然後把這個成功的地方移除, 剩下的再繼續轉.

commons icon [10] Re:phpBB 由 BIG5 轉換到 UTF-8 [ 回覆 ]

請問要如何把韓國的編碼加進這個轉碼工具來處理韓國編碼文字啊?

commons icon [11] Re:phpBB 由 BIG5 轉換到 UTF-8 [ 回覆 ]

沒用過韓文. 可能是 EUC-KR, UHC (CP949), JOHAB, ISO-2022-KR, KOI8-R 這幾種編碼吧. 在 $encoding 這個陣列加上這幾個試看看吧.

commons icon [12] Re:phpBB 由 BIG5 轉換到 UTF-8 [ 回覆 ]

只要加上只要加上編碼文字就可以了嗎?

那下面的:

function code2utf($num)
{
// Returns the utf string corresponding to the unicode value
// courtesy - romans@void.lv
if ($num6)+192).chr(($num&63)+128);
if ($num>12)+224).chr((($num>>6)&63)+128).chr(($num&63)+128);
if ($num>18)+240).chr((($num>>12)&63)+128).chr((($num>>6)&63)+128). chr(($num&63)+128);
return '';
}

那些數字代表什麼意思啊?

commons icon [13] Re:phpBB 由 BIG5 轉換到 UTF-8 [ 回覆 ]

當你舊的系統不是 UTF-8 的時候, 有些程式會把 UTF-8 的字存成 & # xxxxx ; 之類的碼, 這樣子在網頁顯示時, 才能看到這個字.
上頭的程式會用 num2utf() 把那個數字轉成 UTF-8 編碼的數字, 然後 code2utf8() 把轉出來的數字, 轉成 UTF-8 的文字.

commons icon [14] Re:phpBB 由 BIG5 轉換到 UTF-8 [ 回覆 ]

了解,那在同一行不同編碼會被移除的問題解決了嗎?
因為我拿 AppServ2.4.7 安裝 phpBB 2.0.22 Big5版,我在裡面故意輸入了Big5 會有的反斜線字碼,然後在該行加了日文進去
可是用這個工具轉出來的資料庫沒有發生這個問題,只是我有一個要轉的正式資料庫,怕說會不會因此資料庫受到毀損
請問前輩可以回答這個問題嗎?

commons icon [15] Re:phpBB 由 BIG5 轉換到 UTF-8 [ 回覆 ]

同一行上頭的程式應該只會轉一種編碼.
我這兒沒有這樣的環境與需求, 所以目前不會去改這個.

至於轉出來有沒有問題... 就轉看看吧, 反正不要存回同一個資料庫就不會有問題.

commons icon [16] Re:phpBB 由 BIG5 轉換到 UTF-8 [ 回覆 ]

糟糕了,我要轉的資料庫有簡體, 繁體, 還有日文 (容量大約250.MB)
而且數量為數不少, 都需要轉成UTF-8編碼, 也不能漏掉...

請問前輩能不能想個辦法啊?

commons icon [17] Re:phpBB 由 BIG5 轉換到 UTF-8 [ 回覆 ]

轉了不就知道了嗎? 不能用再來說吧.
真的有那麼多是不同編碼在同一行嗎? 想不出為什麼會有人在同一行打了不同編碼的字進去. 一般同一個人打的文章, 不應該是用同一種編碼嗎?
而且通常在這類非 unicode 編碼的網頁, 如果可以看到正確的文字, 就表示實際存放的應該是類似 & # nnnn; 這一類被另外編碼的方式儲存, 才能正確的在 browser 上頭被看到. 如果是這樣子的儲存方式, 用這個程式轉換, 並不會有什麼問題.

commons icon [18] Re:phpBB 由 BIG5 轉換到 UTF-8 [ 回覆 ]

不好意思,這麼久才來回應
我只想說真的有人會在同一行插入不同編碼的文字在上面,例如日韓偶像明星俱樂部
他們會常常在同一行插入中文來做對照翻譯,像這樣的網站真的很多
所以在沒有十足的把握下,我不太敢直接正式轉碼...
既然您這麼說了,那我就先試試看吧!

commons icon [19] Re:phpBB 由 BIG5 轉換到 UTF-8 [ 回覆 ]

請問這篇是在什麼環境下?
OS是MS-Windows或Linux? 其他?
MySQL版本 ?
my.cnf內容 ?
執行 mysql , 執行 show variables; 有關編碼的設定為何?
MySQL是隨OS安裝? 還是自己compile ? 有用 --with-charset設定?
謝謝 ~

commons icon [20] Re:phpBB 由 BIG5 轉換到 UTF-8 [ 回覆 ]

這篇提到的東西, 與 OS 沒關係, 與 MySQL 的設定也沒關係. 就只是把 select 出來可以看到正確的資料再轉成 UTF-8 的 SQL 指令.

迴響
暱稱:
標題:
個人網頁:
電子郵件:
迴響

  

Bad Behavior 已經阻擋了 175 個過去 7 天試圖闖關的垃圾迴響與引用。
Power by LifeType. Template design by JamesHuang. Valid XHTML and CSS