PukiwikiをEUC-JPからUTF-8化した。以下そのメモ。
***方針 [#bd3542c2]
-一般的な文字コード変換ツールのeuc-jpからは変換しない。一部のページでeuc-jpのmicrosoft拡張cp51932の文字を使ってしまっていたのでeuc-jpのかわりにcp51932からutf-8に変換する。iconv, tcl不可。(ページのタイトルは常用漢字しか使ってなかったのでこれはeuc-jpからutf-8に変換するだけでいい)
-一般的な文字コード変換ツールのeuc-jpからは変換しない。一部のページでeuc-jpのmicrosoft拡張cp51932の文字を使ってしまっていたので文字化けしてしまう。euc-jpのかわりにcp51932からutf-8に変換する。''iconv不可''。(ページのタイトルは常用漢字しか使ってなかったのでこれはeuc-jpからutf-8に変換するだけでいい)
-新旧ページのタイムスタンプを維持する。
-旧URLからのアクセスを新URLにリダイレクトする。



***ファイル名の変換の仕方 [#ed739fd8]
ファイル名はeuc-jpの文字列のバイナリをHEXにしただけなので、これをutf-8に変換すればよい。このようなスクリプトを使った。
 proc convert_filename {filename {notchange 0}} {
     set rootname [file rootname $filename]
     set ext [file ext $filename]
 
     set name [encoding convertfrom euc-jp [binary format H* $rootname]]
 
     if {[string is ascii $name]} {
         set newname $filename
     } else {
         set bin [encoding convertto utf-8 $name]
         binary scan $bin H* bstr
         set newname [string toupper $bstr]$ext
     }
     if {!$notchange} {
         file rename -force $filename $newname
     }
     return $newname
 }


***ファイルの中身の変換の仕方 [#n98f55ac]
先に述べたように、これはeuc-jpからではなく、cp51932から変換する。これはmlang.dllというIE付属のライブラリを使ってTclの拡張をCで適当に書き捨てることにした。
 #include <windows.h>
 #include <tcl.h>
 
 typedef HRESULT (APIENTRY *LPCONVERTINETSTRING)(LPDWORD, DWORD, DWORD, LPCSTR, LPINT, LPBYTE, LPINT);
 typedef HRESULT (APIENTRY *LPISCONVERTINETSTRINGAVALABLE)(DWORD, DWORD);
 
 static HANDLE hDLL = NULL;
 LPCONVERTINETSTRING ConvertINetString = NULL;
 LPISCONVERTINETSTRINGAVALABLE IsConvertINetStringAvailable = NULL;
 
 static BOOL InitDLL()
 {
     if(hDLL == NULL){
     hDLL = LoadLibrary("mlang.dll");
         if(hDLL == NULL){
             return FALSE;
         }
         ConvertINetString = (LPCONVERTINETSTRING)
             GetProcAddress(hDLL, "ConvertINetString");
         IsConvertINetStringAvailable = (LPISCONVERTINETSTRINGAVALABLE)
             GetProcAddress(hDLL, "IsConvertINetStringAvailable");
     }
    return TRUE;
 }
 
 
 int Eucjp_ReadCmd (ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj *objv[])
 {
     HRESULT result;
     DWORD mode = 0;
     unsigned char *barray = NULL;
     unsigned char *euc = NULL;
     unsigned char *utf = NULL;
     int len, euclen, utflen;
     if (objc <= 1) {
         return TCL_OK;
     }
     barray = Tcl_GetByteArrayFromObj(objv[1], &len);
     if (len == 0) {
         return TCL_OK;
     }
     euclen = len;
     utflen = euclen * 3;
     euc = calloc(euclen, sizeof(char));
     utf = calloc(utflen, sizeof(char));
     memcpy(euc, barray, len);
     
     result = ConvertINetString (
         &mode,
         51932,
         65001,
         euc, &euclen,
         utf, &utflen);
     Tcl_SetObjResult(interp, Tcl_NewStringObj(utf, utflen));
     if(euc) free(euc);
     if(utf) free(utf);
     return TCL_OK;
 }
 
 
 DLLEXPORT int Eucjp_Init (Tcl_Interp *interp)
 {
     if (InitDLL() == FALSE) {
         return TCL_ERROR;
     }
 #ifdef USE_TCL_STUBS
    if (Tcl_InitStubs(interp, "8", 0) == NULL) {
        return TCL_ERROR;
    }
 #endif
    Tcl_CreateObjCommand(interp, "read_eucjp", Eucjp_ReadCmd, NULL, NULL);
 
    if (Tcl_PkgProvide(interp, "Eucjp", "1.0") != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
 }
簡単にこんなもんで。これでread_eucjpというコマンドを作成した。これをコンパイルしてloadすればよい。これはcp51932のバイト配列を受け取ってTclの文字列に変換して返す。これをスクリプト側で
 proc convert_file_contents {filename} {
     set fp [open $filename r]
     fconfigure $fp -encoding binary -translation binary
     set contents [read $fp]
     close $fp
 
     set fp [open $filename w]
     fconfigure $fp -encoding utf-8 -translation lf
     puts -nonewline $fp [read_eucjp $contents]
     close $fp
 }
みたいなファイル名を渡すだけで中身を変換するというラッパーに包むと簡単に変換することができる。



***リダイレクト [#o170271e]
euc-jpでのURL名と、それを変換したutf-8のURL名を出力して、.htaccessにコピペする。ここでascii文字だけで構成されたページ名はutf-8でもeuc-jpでも同一なので、これは出力しないようにする。URL名は[[その辺に転がっているcgiライブラリ:http://tcllib.sourceforge.net/doc/ncgi.html]]で変換することにした。
またファイル名を変換する前のwikiディレクトリの中身から作成した。処理の流れはファイル名からページ名を読み込み、日本語を含む場合はutf-8に変換してファイル名を作成して出力する。
 proc output_redirect {output_file} {
     set fp [open $output_file w]
     set pwd [pwd]
     cd ${::originaldir}/wiki
     foreach f [glob -type f *.txt] {
         set eucname [file rootname $f]
         set tclname [encoding convertfrom euc-jp [binary format H* $eucname]]
         if {[string is ascii $tclname]} {
             continue
         }
         set from [ncgi::encode [encoding convertto euc-jp $tclname]]
         set to   [ncgi::encode [encoding convertto utf-8 $tclname]]
         puts $fp "Redirect permanent /wiki/${from}.html http://reddog.s35.xrea.com/wiki/${to}.html"
     }
     cd $pwd
     close $fp
 }
 output_redirect [file join [file dir [info script]] redirect.txt]
URLに.htmlがついてるのはこのサイトが色々小細工しているため。この出力結果を.htacessにコピペすればよい。
これで例えば、/wiki/GIMP2%A4%C7%BD%C4%BD%F1%A4%AD.htmlへのアクセスが/wiki/GIMP2%E3%81%A7%E7%B8%A6%E6%9B%B8%E3%81%8D.htmlにリダイレクトできる。



***各ディレクトリごとにやったこと。 [#q097fc87]
-attach~
_(アンダースコア)で繋いだ"*_*.log", "*_*", "*_*.1" のファイル名を変換する。しかし、_と拡張子はそのまま残すようにする。ファイルの中身は変更しない。またタイムスタンプを記録しておく。
-backup~
バックアップはgzで圧縮されているので、解凍して、ファイル名と中身を変換してから再圧縮する。
-cache~
拡張子が.relと.refのファイル名と中身を変換する。recent.datの中身を変換する。
-counter~
使用してなかったので何もせず。
-diff~
拡張子が.txtのファイル名と中身を変換する。
-image~
画像なのでなにもしない
-lib~
拡張子が.phpの中身を変換する。init.phpの//UTF-8:define('PKWK_UTF8_ENABLE', 1)のコメントを外してdefine('PKWK_UTF8_ENABLE', 1)にする。
-plugin~
拡張子が.phpの中身を変換する。search.inc.phpで"EUC-JP"の文字列を"UTF-8"に変換する。
-skin~
pukiwiki.skin.phpだけ中身を変換した。携帯用スキンがよくわかってないけど、とりあえずよしとする。
-trackback~
使用してなかったので何もせず。
-wiki~
拡張子が.txtのファイルのファイル名と中身を変換する。タイムスタンプを保存しておく。
-ルート~
全てのファイルの中身をutf-8に変換する。



***タイムスタンプの維持 [#b5d35445]
とりあえず、ファイルのエンコーディングの変換時にタイムスタンプを維持するようにしておいたので、ftpでアップロード後、スクリプトを回してサーバー上でタイムスタンプを変更する。ファイルの変換時にタイムスタンプを維持するのは、
 namespace eval timestamp {
     variable atime
     variable mtime
     proc save {f} {
         variable atime
         variable mtime
         set atime [file atime $f] 
         set mtime [file mtime $f]
     }
     proc load {f} {
         variable atime
         variable mtime
         file atime $f $atime
         file mtime $f $mtime
     }
 }
このようなものを用意しておいて変換処理の前後を挟むだけ。
 timestamp::save $file
 convert_file_contents $file
 set file [convert_filename $file]
 timestamp::load $file
みたいにするだけ。これで変換前と変換後のファイルでタイムスタンプが同じになる。ftpのアップロードではファイルのタイムスタンプが最新に変更されてしまうので、サーバー上でタイムスタンプを変更するスクリプトを書く。ローカルで
 set fp [open timestamp.cgi w]
 fconfigure $fp -translation lf
 puts $fp "#!/usr/bin/tclsh"
 puts $fp {puts "Content-type: text/html\n\n"}
 foreach {dir pattern} {attach *_* wiki *.txt} {
     cd $dir
     set dir ../wiki/${dir}/
     foreach f [glob -type f $pattern] {
         puts $fp "file mtime $dir$f [file mtime $f]"
         puts $fp "file atime $dir$f [file atime $f]"
     }
     cd ..
 }
 close $fp
みたいなスクリプトを回してcgiファイルを作成し、これをアップロードしてサーバ側で実行すればよい。タイムスタンプを同期させなければならないのは、attachとwikiだけのようだ?

***スクリプトの残骸 [#s19391ee]
#ref(eucjp2utf8.zip)

***コメントをどーぞ [#s65dd056]
#comment
----
[[CategoryPukiwiki]]

|New|Edit|Diff|History|Attach|Copy|Rename|
HTML convert time: 0.003 sec.