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だけのようだ?

スクリプトの残骸

コメントをどーぞ



CategoryPukiwiki


Attach file: fileeucjp2utf8.zip 279 download [Information]

|New|Edit|Freeze|Diff|History|Attach|Copy|Rename|
Last-modified: 2007-05-31 (Thu) 16:12:05
HTML convert time: 0.012 sec.