PukiwikiをEUC-JPからUTF-8化した。以下そのメモ。
ファイル名は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 }
先に述べたように、これは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 }
みたいなファイル名を渡すだけで中身を変換するというラッパーに包むと簡単に変換することができる。
euc-jpでのURL名と、それを変換したutf-8のURL名を出力して、.htaccessにコピペする。ここでascii文字だけで構成されたページ名はutf-8でもeuc-jpでも同一なので、これは出力しないようにする。URL名はその辺に転がっているcgiライブラリで変換することにした。 またファイル名を変換する前の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にリダイレクトできる。
とりあえず、ファイルのエンコーディングの変換時にタイムスタンプを維持するようにしておいたので、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だけのようだ?