«前の日記(2005-12-31 [土]) 最新 次の日記(2006-01-31 [火])» 編集
RSS: href="http://endoh-namazu.tierra.ne.jp/diary/index.rdf"


半期 四半期 全カテゴリ

新・なまず日記


2006-01-11 [水]

_ [AHK]AutoHotkeyのダメ文字問題対策

かなり遅くなってちょっとマヌケですが、みなさんあけましてオメデトございます。本年もなまず日記をよろしくお願いします。

さて、2006年最初の書き込みは、ちょっとしたライブラリを提供したいと思います。

AutoHotkeyは英語圏のソフトなので、日本語が混じった文字列を扱うと、いわゆる「ダメ文字問題」が発生します。例えば、ファイルのフルパスからファイル名を取り出すコマンドSplitPathで、 "c:\2006年度予算\研究材料予算.xls"というフルパスを処理してみましょう。

  test := "c:\2006年度予算\研究材料予算.xls"
  SplitPath, test , OutFileName

OutFileNameにはファイル名が入るはずですが、結果は、"算.xls"となります。イヤになっちゃいますね。

ダメ文字問題がどうして起こるのか、ということについては、なまずより詳しい方が沢山いると思うので、ここでは概要だけ述べておきますと、

  1. 日本語(Shift-JIS)は2バイトで一文字なのに、AHKでは一文字を1バイトで取り扱う。
  2. 日本語(Shift-JIS)の2バイトのうちの後ろの1バイトで、英語に使う文字コードと同じコードが出てきてしまう。

といったところでしょうか。上記の例で言えば、"予算"の の字は、Shift-JISでは、0x975cという2バイトの文字コードであり、また、フォルダとファイル名の区切り文字である"\"は、0x5cという1バイトの文字コードなのです。英語圏のソフトは、の字が2バイトで構成されていることを知らず、単に、"\"(0x5cという文字)を探しにいくので、の字の下位1バイトを、"\"、つまりフォルダとファイル名の区切り文字であると勘違いしてしまい、上記のような悲惨な結果になる、というわけです。こういう場合、は、ファイル名としては使えない、つまりダメ文字、と呼ぶらしいです。でも、そんなこといちいち考えてファイル名を考えなければいけないなんて、やってられないですよね。

以前、「AutoHotkeyを流行らせるページ」の掲示板に、「AHKってダメ文字あるよね。ソースコードの中で、1バイト文字列処理関数を使っているから、これを2バイト系に変えればいいんじゃないかな」という書き込みをしたら、「じゃ、よろしく(おまえがやれよ)」とのレスを頂きました。

確かに、いいだしっぺがなんとかするのが、この世界(?)のオキテであります。ヨロシクされた以上、なまずがなんとかしなければならないのですが、しかし、AHKのソースコードを一度いじってしまったら、なまずは一生、AHKにパッチをあて続けなければなりません。バージョンアップが頻繁なAHKにあわせてパッチをあて続けるのは、あまりやりたい作業ではありません。

もっとスマートな解決手段はないものかと考えて、DllCallを使って、日本語(マルチバイト文字列=Shift-JIS、もっと正確に言うと、Windows-31Jというんですか?詳しい方教えて下さい)処理ライブラリを作ってみました。ようするに、内部でワイドキャラ(UNICODE、UTF-16になるのかな?)に変換して、あれこれやって、またマルチバイト文字列に戻すわけです。AHKのオリジナルの文字列処理コマンドの代わりに、このライブラリで用意した関数をコールすれば、ダメ文字問題は解決されます。Sortコマンドを除くほとんどの文字列処理コマンドに対応しました。あと、ちょっとしたおまけ関数も追加しました。

上記でおわかりだとは思いますが、日本語処理ライブラリといっても、Shift-JIS(Windows-31J?)の範囲の漢字が文字化けなく扱えるようになる、というだけで、例えば「吉田さん」のの字の、上のところがになっているような漢字が使えるようになるなんて、夢のような話では全然ありませんので、ご了承ください(あたりまえか)。

以下のファイルをダウンロードして、解凍した後、中の"mbstring.ahk"をインクルードしてお使いください。
mbstring_v00900.zip

以下、各関数の仕様を、AHKオリジナルのコマンドと対比する形で、説明します。


  • InStrの代わり

    pos := MBS_InStr( inputVar, searchText [, caseSnstv, startingPos ] )

    引数:
    • inputVar
      検査する文字列が入った変数(オリジナルと同じ)
    • searchText
      検索文字列(オリジナルと同じ)
    • caseSnstv
      trueで、大文字小文字の区別をする。省略時はfalse (オリジナルと同じ)
    • startingPos
      検査開始文字指定(オリジナルと同じ)

    戻り値:
    • pos
      searchTextinputVarの中で最初に出現する位置を返す。出現しなかったら0(= false)。(オリジナルと同じ)
    動作:
    • ダメ文字問題がない以外、オリジナルのInStrと同じ。全角文字も1文字と数える。


  • If var is typeの代わり

    result := MBS_IfVarIsType( var, type )

    引数:
    • var
      検査する文字列が入った変数(オリジナルと同じ)
    • type
      検索する型をあらわす文字列 オリジナルと同じ型が指定できるが、追加で以下の型も 指定できる。
      • hankaku : varがすべて半角だったらtrue。半角カナも半角とみなす。
      • zenkaku : varがすべて全角だったらtrue

    戻り値:
    • result
      varがすべてtypeで指定した型だったらtrue,そうでなければfalseが返る(オリジナルと同じ)

    動作:
    • typeが"integer", "float", "number", "time"の場合、varがすべて半角だったら、オリジナルのIf var is typeをコールする。全角だったら、falseを返す。
    • "hankaku", "zenkaku", "digit", "xdigit", "alpha", "upper", "lower", "alnum", "space"だったら、全角、半角を考慮した検査を行う。例えば、"digit"の場合、全角の123でも、trueが返る。また、"space"だった場合は、全角の空白もtrueが返る。
    • それ以外は、オリジナルと同じ。

    使用例:
     if(MBS_IfVarIsType(test,"hankaku")
       MsgBox, test is hankaku.
     else
       MsgBox, test is not hankaku.
          


  • Loop,PARSEの代わり

    result := MBS_ParseBegin( inputVar [, delimiters, omitChars, mark ] )

    引数:
    • inputVar
      分割される文字列が格納された変数名(オリジナルと同じ)
    • delimiters
      区切り文字として使用したい文字を列挙。全角文字も指定できる。オリジナルと同じ意味だが、"CSV"は指定できない。

    • omitChars
      各フィールドの先頭と末尾から取り除きたい文字を列挙。全角文字も指定できる。
    • mark
      MBS_ParseReturn()で抜ける際、抜け出すループを指定する。(後述)

    戻り値:
    • result
      成功時はtrue,メモリ取得に失敗した場合はfalse。 しかし、おそらくメモリ取得に失敗した場合は、AHK自体がまともに動かない だろうから、チェックはあまり意味がないと思う。


    index := MBS_Parse( loopField [, method ] )

    引数:
    • loopField
      切り取られた文字列が格納される。オリジナルのA_LoopFieldと同じ。
    • method
      おまけ機能。
      1を指定すると、右端の文字列を切り取り、loopFieldに格納する。2を指定すると、切り取られた後の残りの文字列すべてをloopFieldに格納する。省略時は、通常通り、左端の文字列を切り取り、loopFieldに格納する。1指定時(右端切り取り時)は、左端指定時と同じように、内部に保存している文字列は短くなる。つまり、1を指定してMBS_Parse()をコールすれば、右端から、parseしていくことになる。2指定時(残り文字列取り出し時)は、切り取り動作は行わない。つまり、2を指定してMBS_Parse()を何度呼んでも、そのたびにloopFieldは同じ値が入り、変化しない。

    戻り値:
    • index
      ループ回数。オリジナルのA_Indexと同じ。すべてParseし終わると、0が返る。その場合、loopFieldはNULLが格納される。


    MBS_ParseEnd()

    引数:
    • なし

    戻り値:
    • なし

    MBS_ParseReturn()

    引数:
    • なし

    戻り値:
    • なし


    動作:
    • Loop, PARSEと同様な動作をさせるための関数。以下の記述は、まったく同じ意味となる。
       ; オリジナル
       Loop, PARSE, inputVar1, `,
       {
         Loop, PARSE, inputVar2, `,
         {
           MsgBox, %A_LoopField%
         }
       }
      
      ; 本関数(上記のオリジナルと同じ意味) MBS_ParseBegin(inputVar1,",") Loop { if(!MBS_Parse(loopField)) break
      MBS_ParseBegin(inputVar2,",") Loop { if(!MBS_Parse(loopField)) break
      MsgBox, %loopField% } MBS_ParseEnd() } MBS_ParseEnd()
    • MBS_ParseBegin()と、MBS_ParseEnd()とは、必ずループの外に、セットで 存在しなければならない。メモリが許す限り、MBS_ParseBegin()で始まるループは、何回でも入れ子にすることができる。ただし、ループの外で、必ず対応するMBS_ParseEnd()をコールしなければならない(そうしないと、入れ子の外に出たことを認識できない)。
    • オリジナルと同じように、inputVarの内容は、内部に取り込まれるので、 MBS_ParseBegin()の後で、自由に内容を変更してよい。
    • MBS_ParseEnd()をコールせずに、一気に入れ子を脱出する場合(ループの中から、関数をreturnしてしまう場合など)は、MBS_ParseReturn()をコールする。すると、一番最近にmarkをtrueにしたMBS_ParseBegin()のループまでが、解消(内部に保持した文字列の廃棄)される。
       ; オリジナル
       Loop, PARSE, inputVar1, `,
       {
         Loop, PARSE, inputVar2, `,
         {
           if(A_LoopField == "end")
             return
         }
       }
      
      ; 本関数(上記と同じ意味) MBS_ParseBegin(inputVar1,",","",true) ; markをtrueに Loop { if(!MBS_Parse(loopField)) break
      MBS_ParseBegin(inputVar2,",") Loop { if(!MBS_Parse(loopField)) break
      if(loopField == "end") { MBS_Return() ; returnの前に、かならずMBS_Return()を呼ぶ ; このコールで、一番最近markをtrueにした ; ループ(この場合はinputVar1のループ)までが、 ; 解消される。 return } } MBS_ParseEnd() } MBS_ParseEnd()
    • MBS_Parse()のmethodの使い方は、以下を参照のこと。
       test := "a,b,c,d"
      
      MBS_ParseBegin(test,",")
      index := MBS_Parse(loopField) ; loopField = "a" index = 1 index := MBS_Parse(loopField,1) ; loopField = "d" index = 2 index := MBS_Parse(loopField,2) ; loopField = "b,c" index = "" index := MBS_Parse(loopField) ; loopField = "b" index = 3 index := MBS_Parse(loopField) ; loopField = "c" index = 4 index := MBS_Parse(loopField) ; loopField = "" index = 0
      MBS_ParseEnd()
    • オリジナルと異なり、delimiters に"CSV"を指定することができない。(多分)"CSV"では、ダメ文字問題が発生しないので、オリジナルのLoop,PARSEの方をお使いになることをお勧めする。


  • StringLowerの代わり

    MBS_StringLower( outputVar, inputVar [, title ] )

    引数:
    • outputVar
      変換後の文字列を格納する変数名(オリジナルと同じ)
    • inputVar
      変換もとの文字列の入った変数の名前
    • title
      "T"(ほんとは、NULL以外を指定すればなんでもいい)を指定すると、単語の先頭だけが大文字で後は小文字の形式に変換される。

    戻り値:
    • なし

    動作:
    • 英字を小文字にする。全角の英字も小文字になる。実は、titleを指定した時以外は、標準のStringLowerを呼んでいる。titleを指定した場合は、標準ではダメ文字問題が出るので、この関数で処理している。

    使用例:
     ; inputVarを、"Title Format"(this を、Thisに)にして、outputVarに
     MBS_StringLower(outputVar, inputVar, "T")
          


  • StringUpperの代わり

    MBS_StringUpper( outputVar, inputVar [, title ] )

    引数:
    • outputVar
      変換後の文字列を格納する変数名(オリジナルと同じ)
    • inputVar
      変換もとの文字列の入った変数の名前
    • title
      "T"(ほんとは、NULL以外を指定すればなんでもいい)を指定すると、単語の先頭だけが大文字で後は小文字の形式に変換される。

    戻り値:
    • なし

    動作:
    • 英字を大文字にする。全角の英字も大文字になる。実は、titleを指定した時以外は、標準のStringUpperを呼んでいる。titleを指定した場合は、標準ではダメ文字問題が出るので、この関数で処理している。


  • StringLeftの代わり

    MBS_StringLeft( outputVar, inputVar , count )

    引数:
    • outputVar
      抜き出した文字列を格納する変数名(オリジナルと同じ)
    • inputVar
      抜き出す元の文字列の入った変数名(オリジナルと同じ)
    • count
      抜き出す文字数(オリジナルと同じ)

    戻り値:
    • なし

    動作:
    • count は、全角も1文字と数える。それ以外は、オリジナルと同じ


  • StringRightの代わり

    MBS_StringRight( outputVar, inputVar , count )

    引数:
    • outputVar
      抜き出した文字列を格納する変数名(オリジナルと同じ)
    • inputVar
      抜き出す元の文字列の入った変数名(オリジナルと同じ)
    • count
      抜き出す文字数(オリジナルと同じ)

    戻り値:
    • なし

    動作:
    • count は、全角も1文字と数える。それ以外は、オリジナルと同じ


  • StringTrimLeftの代わり

    MBS_StringTrimLeft( outputVar, inputVar , count )

    引数:
    • outputVar
      抜き出した文字列を格納する変数名(オリジナルと同じ)
    • inputVar
      抜き出す元の文字列の入った変数名(オリジナルと同じ)
    • count
      抜き出す文字数(オリジナルと同じ)

    戻り値:
    • なし

    動作:
    • count は、全角も1文字と数える。それ以外は、オリジナルと同じ


  • StringTrimRightの代わり

    MBS_StringTrimRight( outputVar, inputVar , count )

    引数:
    • outputVar
      抜き出した文字列を格納する変数名(オリジナルと同じ)
    • inputVar
      抜き出す元の文字列の入った変数名(オリジナルと同じ)
    • count
      抜き出す文字数(オリジナルと同じ)

    戻り値:
    • なし

    動作:
    • count は、全角も1文字と数える。それ以外は、オリジナルと同じ


  • StringMidの代わり

    MBS_StringMid( outputVar, inputVar , startChar, count [, dir ] )

    引数:
    • outputVar
      抜き出した文字列を格納する変数名(オリジナルと同じ)
    • inputVar
      抜き出す元の文字列の入った変数名(オリジナルと同じ)
    • startChar
      取り出す部分の開始位置(オリジナルと同じ)
    • count
      抜き出す文字数(オリジナルと同じ)
    • dir
      "L"(実はNULLでなかったらなんでもいい)を指定すると、StartChar以前(左)の部分をCount文字だけ取り出す(オリジナルと同じ)

    戻り値:
    • なし

    動作:
    • startCharcount は、全角も1文字と数える。それ以外は、オリジナルと同じ


  • StrLenの代わり

    len := MBS_StrLen( var )

    引数:
    • var
      検査する文字列

    戻り値:
    • len
      文字列の長さ

    動作:
    • len は、全角も1文字と数える。それ以外は、オリジナルと同じ


  • StringGetPosの代わり

    pos := MBS_StringGetPos( inputVar, searchText [, dir , offset, caseSnstv ] )

    引数:
    • inputVar
      検査する文字列の入った変数名(オリジナルと同じ)
    • searchText
      検索する文字列(オリジナルと同じ)
    • dir
      "1"とか、"L2"とか、"R3"とか指定する。省略時は"L1"と同じ(オリジナルと同じ)
    • offset
      検索時、最初のOffset文字分だけ無視して検索を始める。省略時は"0"と同じ(オリジナルと同じ)
    • caseSnstv
      trueのとき、大文字小文字を区別する。省略時は区別しない。

    戻り値:
    • pos
      検索された位置。文字列の最初は「0」として数える。(オリジナルのoutputVarと同じ)。offsetがマイナスだったり、dirLRの値が上記定義以外だったり、inputVarかsearchTextがNULLだった場合は、-2が返る。

    動作:
    • pos は、全角も1文字とみなす。それ以外は、オリジナルと同じ


  • StringReplaceの代わり

    err := MBS_StringReplace( outputVar, inputVar, searchText [, replaceText, replaceAll, caseSnstv ] )

    引数:
    • outputVar
      置換結果の文字列を格納する変数(オリジナルと同じ)
    • inputVar
      置換前の文字列を格納している変数(オリジナルと同じ)
    • searchText
      検索する文字列(オリジナルと同じ)
    • replaceText
      searchTextが置き換えられる先の文字列(オリジナルと同じ)
    • replaceAll
      省略するか、"1"なら、最初の一回だけ、置換する。それ以外なら、すべての文字列を置換する。
    • caseSnstv
      trueのとき、大文字小文字を区別する。省略時は区別しない。

    戻り値:
    • err
      置換回数を返す。-1のときは、メモリが取得できなかったことを示す。

    動作:
    • ダメ文字問題がない以外は、オリジナルと同じ


  • StringSplitの代わり、というか、手伝い。

    dlmt := MBS_StringSplit( outputVar, inputVar [, delimiters, omitChars, outputVarDelimiter ] )

    引数:
    • outputVar
      分割(?)結果の文字列を格納する変数
    • inputVar
      分割前の文字列を格納している変数(オリジナルと同じ)
    • delimiters
      区切り文字として使用したい文字を列挙(オリジナルと同じ)。全角文字も使える。
    • omitChars
      分割された各要素の最初と最後から取り除く文字を列挙 (オリジナルと同じ)。全角文字も使える。
    • outputVarDelimiter
      outputVarに格納される文字列の区切り文字を指定する。省略時には、区切り文字は、0x01というコードが使われる。

    戻り値:
    • dlmt
      outputVar に格納される文字列の区切り文字が返る。outputVarDelimiter を省略したら、常に0x01。outputVarDelimiter を指定したら、常に、outputVarDelimiter と同じ値。

    動作:
    • オリジナルのStringSplitは、分割した各フィールドが、 array1, array2 といった疑似配列変数に格納されるが、関数では、そのような動作は実現できない。しかし、オリジナルのStringSplitでは、ダメ文字問題が発生してしまう。
    • そこで、本関数では、ダメ文字問題が発生しない形で inputVar を分割し、それをShift-JISでもASCIIでも使わない0x01というコードを区切り文字とする一つの文字列として、 outputVar に格納する。0x01で分割する分には、少なくともShift-JISに関しては、ダメ文字問題は絶対に起こらないので、オリジナルのStringSplitを使えばよい。
    • 以下のコードは、まったく同じ意味となる。
       ; オリジナル
       inputVar := "a,b,c"
       StringSplit, array, inputVar, `,
      
      ; 本関数 inputVar := "a,b,c" dlmt := MBS_StringSplit(tmp, inputVar, ",") StringSplit, array, tmp, %dlmt%
    • 区切り文字0x01は、Shift-JISでもASCIIでも通常は絶対に使わない文字だが、もし、 inputVar の中で、あえてこのコードを使っていると、当然、分割時に誤動作する。そこで、そのような場合は、 outputVarDelimiter で、任意の文字を指定する必要がある。ダメ文字問題を起さないためには、その時指定する文字は、0x3e以下の値である必要がある。


  • SplitPathの代わり

    MBS_SplitPath( InputVar , outFileName, outDir, outExtension, outNameNoExt, outDrive )

    引数:
    • inputVar
      分解するファイルパスを格納した変数
    • outFileName
      オリジナルと同じ。ただし、省略は不可
    • outDir
      オリジナルと同じ。ただし、省略は不可
    • outExtension
      オリジナルと同じ。ただし、省略は不可
    • outNameNoExt
      オリジナルと同じ。ただし、省略は不可
    • outDrive
      オリジナルと同じ。ただし、省略は不可

    戻り値:
    • なし

    動作:
    • ダメ文字問題がない以外は、オリジナルと同じ。ただし、outFileNameなどは、ByRefを使っているので、省略できないのが残念なところ。必要のない変数に関しては、dummy とか、適当な変数を指定しておいてください。


  • 半角文字を、対応する全角文字に変換する。

    MBS_StringZenkaku( outputVar, inputVar [, alpha, num, symbol, space, kana ] )

    引数:
    • outputVar
      変換後の文字列を格納する変数
    • inputVar
      変換前の文字列を格納する変数
    • alpha
      英字を変換する場合はtrue,しない場合はfalse。省略時はtrue
    • num
      数字を変換する場合はtrue,しない場合はfalse。省略時はtrue
    • symbol
      記号変換する場合はtrue,しない場合はfalse。省略時はtrue
    • space
      半角空白(0x20)を、全角空白に変換するときはtrue,しない場合はfalse。省略時はtrue
    • kana
      半角カナを全角カナに変換するときはtrue,しない場合はfalse。省略時はfalse ← 注意。ここだけデフォルトはfalse

    戻り値:
    • なし

    動作:
    • いわゆるASCII文字列(0x20から0x7e)を、対応する全角文字に変換する。
    • また、半角カナも、対応する全角カナに変換する。その場合、半角カナの"バ"などは、ハとテンテンの二文字は一つの全角文字になるので注意。
    • 「対応する全角文字に変換」といっても、なまずが似てる文字を適当に選んでいるので、厳密なものではありません。「こんな変換はねぇだろう」というのがありましたら、是非お知らせください。

    使用例:
     ; inputの半角文字列を変換できるものはすべて全角に
     MBS_StringZenkaku(output,input,true,true,true,true,true)
          


  • 全角文字を、対応する半角文字に変換する。

    MBS_StringHankaku( outputVar, inputVar [, alpha, num, symbol, space, kana ] )

    引数:
    • outputVar
      変換後の文字列を格納する変数
    • inputVar
      変換前の文字列を格納する変数
    • alpha
      英字を変換する場合はtrue,しない場合はfalse。省略時はtrue
    • num
      数字を変換する場合はtrue,しない場合はfalse。省略時はtrue
    • symbol
      記号変換する場合はtrue,しない場合はfalse。省略時はtrue
    • space
      全角空白を、半角空白(0x20)に変換するときはtrue,しない場合はfalse。省略時はtrue
    • kana
      全角カナを半角カナに変換するときはtrue,しない場合はfalse。省略時はfalse ← 注意。ここだけデフォルトはfalse

    戻り値:
    • なし

    動作:
    • 全角文字を、いわゆるASCII文字列(0x20から0x7e)に、変換する。
    • また、全角カナも、対応する半角カナに変換する。その場合、全角カナの"バ"などは、一つの全角文字が、ハとテンテンの二つの半角文字になるので注意。
    • 「対応する半角文字に変換」といっても、なまずが似てる文字を適当に選んでいるので、厳密なものではありません。「こんな変換はねぇだろう」というのがありましたら、是非お知らせください。





さて、文字列操作コマンドのダメ文字問題は、上記の関数でなんとかなるのですが、それ以外にも、AHKには、ダメ文字問題があります。例えば、チンポMsgBoxで出力してみてください。

 MsgBox, チンポ

表示は、ャ塔|となります。なんだこれ?

別に、チンポが下品だからAHKが伏字にした、というわけではないのです。AHKは、デフォルトでは、"`"(バッククオート)が、エスケープ文字になっています。AHKは、スクリプト中の文字列の"`"を探し、多くの場合、これを削除してしまいます。そして、"`"の文字コードは0x60、"チンポ"ののShift-JIS文字コードは0x8360なので、AHKはの2バイト目を削除してしまうのです。すると、残った1バイト目の0x83との1バイト目の0x83がくっついて0x8383、つまりになってしまい、の2バイト目との1バイト目がくっついてとなり...といったことがドミノ倒しのように重なって、上記のような結果になるわけです。チンポと表示できないなんて、困りましたね(←いや、チンポはべつに困らないと思うぞ)

でも、この問題の対処は、とても簡単です。AHKには、#EscapeCharという、エスケープ文字を変更するコマンドがあります。これをつかって、エスケープ文字を、"`"(バッククオート)のような、ダメ文字問題が起こる文字ではなく、Shift-JISでは絶対使わない、0x3e以下の、ダメ文字問題が起きない文字に変更してしまえばいいのです。例えば、"'"(シングルクオート)などです。以下のコードなら、正しくチンポと表示されます。

 #EscapeChar '  ; シングルクオートにエスケープ文字を変更

MsgBox, チンポ

上記のようなことがあるので、汎用的なライブラリを作るような場合は、エスケープ文字を使うのは控えた方がいいですね。なぜなら、汎用的なライブラリを利用する親ルーチンの方で、#EscapeCharでエスケープ文字を変更してしまったら、エスケープ指定が無効になり、あっという間にバグありコードになってしまうからです。また、汎用的なライブラリで、不用意に、エスケープ文字に指定されそうな、記号を文字列として持つのも、好ましくありません。本ライブラリも、Chr()を使って、文字列中に記号を指定しないようにしています。

  col := Chr(0x3a) ; ":"
  sla := Chr(0x2f) ; "/"
  url := "http" . col . sla . sla

かくいうなまずも、この事実に気がついたのは最近のことで、汎用的に使おうと思っていたサブルーチンでバンバンエスケープ文字を使ってしまっていて、トホホ、と思っているところです。これからライブラリを書こうという皆さん、ご注意くださった方がいいと思いますよ。

さてさて、ダメ文字問題対策、いかがでしたでしょうか。AHKプログラマの皆さんにとって、必ず、本ライブラリが必要か、というと、そういうものではないでしょう。AHKというスクリプト言語の性格上、必ずしも日本語処理が必須、ということはないし、作ろうとするスクリプトの仕様によっては、オリジナルの文字列処理コマンドで、ダメ文字問題のない、日本語処理も可能です(なまずIMEも、オリジナルの文字列処理コマンドしか使っていません)。また、当然ながら、本ライブラリよりも、オリジナルの文字列処理コマンドの方が、効率が良く、処理速度が速いにきまっています。

しかし、なまずIMEの時もそうでしたが、ダメ文字問題を考えながらプログラムを作るのは、結構気を使うし、うっとうしいものです。本当に処理したい問題があるのに、あまり本質的でない、ダメ文字を気にするのは、気分の良いものではありません。そういうとき、とりあえず、本ライブラリを使って、本当に処理したい問題が解決できるコードを作ってしまって、処理速度などの問題がでたら、それから、問題のない範囲で、オリジナルの文字列処理コマンドに戻す、そんな使い方をしていただけたら、なまずとしては望外の幸せであります。つまらない問題はライブラリにまかせて、おもしろいプログラミングを楽しもうではありませんか、ご同輩。

また、他の人にAHKを紹介するときでも、「AHKっていいよぉ〜、大抵の処理ができちゃうよ。......日本語処理はちょっとダメだけどね」と言うよりも、「日本語処理も、なまずってバカが作ったライブラリを使えば、なんとかなるみたいよ」などと言ったほうが、ウケが良いのではないでしょうか。そんな感じで、AHKの普及のほんの少しでも力になれたら、なまずとしては大感激であります。

長い記事を読んでいただき、ありがとうございました。今年もなまず日記をよろしくお願いします。

[]