ここでは、JavaScriptによる英数字記号空白/カタカナの半角全角変換とひらがな/カタカナの変換ツール、 文字変換の用途についての考察や、変換処理の実装方法の説明に加え簡単なパフォーマンステストなどを行います。
このツールは元々FormCtrlでの、 フォームに入力された文字の全角/半角変換を行う強制(自動)変換のコードを書いていた時に、 それぞれ対応する文字コードを調べる用途と、その変換のチェックの為に書いたものですが、 結構便利なので変換や文字コード関連ツールとして公開します。
変換処理のコードもMITライセンス形態で公開しています。ダウンロードはこちら。 現状ではほとんどドキュメント的な物は有りませんので、暇な方はソースを読んで自由にご利用下さい。
文字変換機能については、iGoogle用のGadgetとしても利用可能です。 ガジェットを追加
ここでの文字コードは全てUnicodeです。詳しいコード表はUnicode Codes Chartsで。
バグ報告や、ご意見ご要望はFormCtrlのアンケートフォームからどうぞ。
関連記事: PHPでのカタカナ/ひらがなのローマ字変換
文字列の半角/全角変換と、ひらがな/カタカナ変換を行います。
何らかの文字を入力すると、一文字づつコード番号と共に表示されます。
16進数の文字コード番号(3042など)を入力して下さい。入力可能コードはサロゲートペア範囲の10ffffまで。
JavaScriptを利用したクライアント側での文字変換の利用形態としては、サーバー側で生成したデータあるいは他のサイトが提供するデータを自サイト用に表示整形する場合や、 ユーザーの利用するブラウザに合わせた変換などが考えられますが、 最も効果的に用いられるのは、ユーザーにより入力フォームに記入された文字列の自動変換ではないでしょうか。
勿論、フォームに入力され、クライアント側で変換した文字列をそのまま正規化されたデータとして受け入れる事は到底出来ませんので、 クライアント/サーバー側両方にチェックや変換処理を実装する事になりますが、 ユーザーの利便性向上に繋がるクライアント側での自動変換は、それだけのコストを払う価値が有ると考えます。
ただ、あまりにもチェックや変換処理が特殊で、クライアント側にも処理を実装する事が躊躇されるような場合には、 FormCtrlでのAjax利用のように、 個々の入力値の検証にAjaxを利用し毎回サーバー側に問い合わせる事で、 クライアント側での変換と同等の操作感を実現しながら、 チェック/変換処理をサーバー側に一元化するという実装も可能です。
当社で運営していますBlogara:ブロガラのリクエストフォーム(例:テーマ: NFL JAPAN)では、 実在する人物についての名前/フリガナ/生年月日/公式サイトなどの情報を入力して頂きますが、 特にこのような他の情報ソースからの転記となる場合が多い項目では、ユーザーが直接入力するよりも、 文字列のコピー&ペーストでの入力となる頻度が高くなると思われます。
が、情報源の文章やサイトでの文字形式がフォームで要求する形式と異なる場合には、 単にコピー&ペーストしたのでは無効と判断され、結局一々フォームの形式に合わせた言わば単純作業のような変換を手動で行う必要があります。
そこで、入力値の自動変換が実装された入力フォームならば、入力された値を可能な限り要求する形式に変換する事で、 それらの煩わしい作業からユーザーを解放する大きな手助けとなります。
下記の2つの入力フォームはBlogara:ブロガラのテーマリクエストフォームに実装されている、 フリガナと生年月日入力フォームと同様の変換を行います。フォームに入力フォーカスを当てる事で説明文が表示され、入力フォーカスを失った時点で自動変換処理が行われます。
ECMAScript3rdをベースにした2008年現在の多くのPCブラウザでのJavaScript実装では、内部の文字コードにはUnicodeを使用しています。 よってここでも、内部文字コードはUnicodeだという事を前提とします。
英数字記号の半角(halfwidth)と全角(fullwidth)の変換や、ひらがなと全角カタカナへの変換では、 Unicodeの文字コード表での対応する文字の配置(文字の出現順)が同じなので、変換処理もその親切仕様を利用して簡単に行う事が出来ます。
例えば半角英数字と記号では、 U+0021が!で、U+0030が0, U+007eが~となっていますが、これに対応する全角英数字と記号は、 U+ff01が!で、U+ff10が0, U+ff5eが~という配置となっています。
要するに全角の文字コードは対応する半角のコード番号+0xfee0という関係ですので、 変換処理でもこの差分を利用します。
またこの差分による変換は、ひらがなと全角カタカナの変換でも適用出来ます。
変換方法は確定したので、次はそれをどのように実装するかになります。 一般的には、正規表現のreplaceで変換するか、文字列をループ内で一文字づつ処理するか、の2通りの方法が考えられます。
処理内容的にはどちらでも同等の処理が可能ですが、実際の処理はそれなりの差異があります。 そこで各ブラウザでの処理速度を計測してみました。
利用したPCは一昔前のノートPCで、CPU:PentiumM 1GHz Memory:768MB OS:WinXP SP3という構成。
テスト内容は、nikkei.co.jpのトップページのテキストを全て選択してコピー(Ctrl+Aの後Ctrl+C)し、それを数回貼り付けて70KBほどのサイズになったテキストにし、 ひらがな→全角カタカナ, 全角カタカナ→ひらがな変換を十数回繰り返しその平均値を取ります。単位はmsec。
それぞれの処理方法は次の通りです。
ループ内で文字列を一文字づつcharCodeAtで文字コードに変換し、それが処理対象文字か否かをチェック。 処理対象文字ならば差分処理適用後、fromCharCodeで文字コードを文字に戻す。
文字列結合では、str += String.fromCharCode(code)のように文字列結合によって、ループ毎に文字を文字列の最後尾に追加し、 配列結合は、strArray[idx] = String.fromCharCode(code)として配列に格納した後、最後にstrArray.join('')で文字列として出力。
正規表現のパターンで対象文字範囲を指定([\u3041-\u3096]など)、マッチした文字をreplaceの引数で渡すfunction内で処理。 また、ここでの正規表現処理では、正規表現Object(RegExp)と変換functionの作成は前処理段階で行い、それらのObjectを保持したクロージャに対し変換対象テキストを渡している。
function内処理は、ループでの処理と同様に、マッチした文字をcharCodeAtで文字コードに変換し、差分処理を適用した後fromCharCodeで文字として戻す。 変換Object利用は、文字とその文字に対応する文字をObject({'あ':'ア','い':'イ'}など)に格納し、マッチする度に差分処理や文字コード→文字変換処理を行わない。このObjectも前処理段階で生成済み。
処理方法 | IE7.0 | Firefox3.0.4 | Chrome0.3 |
---|---|---|---|
ループ(文字列結合) | 49000 | 140 | 50 |
ループ(配列結合) | 580 | 110 | 90 |
正規表現(function内処理) | 300 | 90 | 130 |
正規表現(変換Object利用) | 140 | 70 | 110 |
ループ(文字列結合)は、GoogleChrome0.3では最も高速ですが、IE7での処理の遅さは全く実用レベルではありません。 また、正規表現(変換Object利用)では、前処理と変換Object保持のコストの割には意外にそれほど高速化には繋がっていない模様です。
さらに、ループと正規表現処理の大きな違いとしては、 ループの場合には、処理速度はそのほとんどを文字数に依存しテキスト内容が変化してもさほど変わりはありませんが、 正規表現の場合には、そのテキストに含まれる対象文字の割合によって大きく変化します。
そこで、下記テストでは88000文字のテキストを用意し、1つは文章の1/4がひらがなで残りは漢字や英数字記号や改行などの一般文章、次に文章からひらがな/カタカナだけを取り出しそれを全てひらがなに変換し88000文字が全てひらがなのテキスト、 最後に全てひらがなのテキストをカタカナに変換し88000文字中に全くひらがなが含まれないテキストを用意し、それぞれに対しひらがな→全角カタカナ変換処理を行います。
上記のテストとは別のPC上のFirefox3.0.4で計測 単位msec処理方法 | 一般文章 | 対象文字のみ | 対象文字無し |
---|---|---|---|
ループ(配列結合) | 290 | 310 | 300 |
正規表現(function内処理) | 210 | 550 | 6 |
正規表現(変換Object利用) | 130 | 380 | 6 |
このように正規表現の場合は、対象文字が全く無い場合には非常に高速に動作し、 対象文字だけのテキストに対しても、変換Object利用ではループと同等の処理速度が実現出来るようです。
というわけで幾分極端なテストが多いですが、FHConvertでは、クロージャを使い前処理を行うタイプの処理では正規表現(変換Object利用)、 直接に変換関数を呼び出す処理では、前処理やObject保持のコスト負担の無いループによる処理としています。(半角/全角カタカナ変換を除く)
半角カナから全角カナへの変換では、他の変換のように1:1(全角カナの濁音/半濁音から半角カナでは2)の関係の変換では無く、後に続く濁点/半濁点を考慮する必要がある為、若干特殊処理になります。
正規表現(変換Object利用)では、defaultで複数文字構成の変換が可能ですが、 半角カナの濁音/半濁音文字の組み合わせを全て正規表現のパターンに設定した場合/(\uff76\uff9e)|(\uff77\uff9e)|(\uff78\uff9e)|...には明らかな速度低下を招きます。 そこで正規表現のパターンには全角の濁音となる組み合わせ以外のパターンも含め/([\uff66-\uff9c]\uff9e)|([\uff8a-\uff8e]\uff9f)|([\uff61-\uff9f])/g、function内での処理と組み合わせて変換を行います。
同様の処理は、ループの場合には現在の位置の文字と次の位置の文字を判定する事で実装可能ですが、 特殊処理を書くのも何なので、半角カナから全角カナへの変換では、クロージャ版か否かに拘わらず正規表現(変換Object利用)で行います。