2021年10月6日水曜日

「JavaScriptで全角数字を半角数字に変換する」正しい方法

 「JavaScriptで全角数字を半角数字に変換する」でGoogle検索するとヒットするほとんどの方法が全角文字の文字コードから65248(0xFEE0)を引くというものだ。こういうコードを平気で書いているところを見ると、あまりプログラミングに詳しくない人が書いたものだと思われる。ここまで来ると「ノイズ」でしかない。あまりにハラがたったので、ちゃんとした方法を書くことにした。

 必要があって、日付文字列を扱うJavaScriptを書くことになった。できれば日本語の日付表記「2021年1月1日」のような形式も扱いたい。場合によっては数字が全角数字になる可能性――たとえば「2021年1月1日」など――もありえる。そこで、JavaScriptで全角数字を半角数字に変換することにした。そうしないと日付を認識させるための正規表現が面倒になるからだ(必ずしもdate.perseが対応しているパターンとは限らないため)。

 ところが「JavaScriptで全角数字を半角数字に変換する」でインターネット検索すると、結果のほとんどが、文字コードを引き算する方法を解説している。いや、ここまで来るとノイズの嵐である。

文字コードを引き算する方法は、たとえば、以下のようなコードになる。

text.replace(/[0-9]/g , function(s) {return  String.fromCharCode( s.charCodeAt(0) - 65248); });

 しかし、これは、文字コードに依存した処理方法だ。一般にプログラミングでは、こうした特定の環境(この場合は文字コード)に依存した方法は避けるようにするのが常道である。また、プログラム中にマジックナンバー(見ただけでは意味のわからない数字)が入るのも普通は避ける。65248を定数定義する方法もあるが、できれば、このようなマジックナンバーが入ることは避けたい。

 もちろん、Unicodeが急に別のコードに変更になるわけではないが、エンコード方法を含めると、将来的にWebの文字コードやエンコード方法が変更になる可能性もないわけではない。

 JavaScriptの国際化や日付を使うライブラリなどを調べてみたが、どうもしっくりくるものがない。JavaScriptで文字コードを計算することなく、全角数字を半角数字に変換する方法はないのか? Twitterでつぶやいたら、親切な方が教えてくれた。

 ちゃん方法があるではないか。いったい、Google検索で出てくる文字コードを引き算するって方法をいまだに正しい方法であるかのように書いているサイトって何? しかし、そういうサイトが大量にあるので、検索結果がこうなってしまうのだろう。

Unicode正規化関数を使う全角数字から半角数字への変換

 具体的には、以下のコードで文字列中の全角数字を半角数字に変換できる。

var newTxt=text.normalize('NFKC');

もちろん、テキストには、数字以外の文字が入っていてかまわない。String.prototype.normalize()とは、Unicodeの「正規化」を行うための関数である。

Unicode正規化とは?

 正規化とは、Wikipediaによれば、

Unicode正規化(ユニコードせいきか、英語: Unicode normalization)とは、等価な文字や文字の並びを統一的な内部表現に変換することでテキストの比較を容易にする、テキスト正規化処理の一種である。

 とされている。

 Unicodeでは、複数のコード(コードポイント)で1つの文字を表したり、あるいは同じ抽象文字を表す別のコードが存在することがある。たとえばひらがな、カタカナの清音と濁点の組み合わせで「濁音」を表すこともできる。たとえば「か」と「゛」の2つのコードで『が』とすることもできれば、「が」という1つのコードでもおなじ『が』が表現できる。

 こうしたルールになっているため、単純な文字列の比較やソートでは、ユニコード文字列が同一かどうかを判断できない。文字列を「正規化」して、同じ抽象文字の並びに対応する複数のユニコード表現を1つのユニコード表現に変換してから比較する必要がある。正規化することで2つのユニコード表現が同一であるかどうかを判定できるようになるわけだ。

 全角数字も半角数字も対応する抽象文字は同一であり、この正規化を使うことで、同一のユニコード表現に変換できる。このときに使われるのが半角数字に相当する文字だ。

 この正規化には、「正準等価性」(きびしいルール。全角数字と半角数字は異なる)、「互換等価性」(ゆるいルール。全角数字と半角数字には同じ)があり、それぞれに「分解形」(方法に従って分解したもの)、「合成形」(分解したあと再度合成)というやりかたがある。

 前記のコードに入っていた「NFKC」は、このうち「互換等価性」の「合成形」を意味する。“Normalization Form Compatibility Composition”の略だ。指定できるキーワードは、以下の4つになる。

NFD      正準等価性    分解形    Normalization Form Canonical Decomposition

NFC      正準等価性    合成形    Normalization Form Canonical Composition

NFKD    互換等価性    分解形    Normalization Form Compatibility Decomposition

NFKC    互換等価性    合成形    Normalization Form Compatibility Composition

ただし、このnormalize('NFKC') を使うと、全角アルファベットも半角アルファベットに変換される。これを避けたい場合には、文字種を判断して変換などおこなう必要がある。たとえば、

text.replace(/[0-9]/g, function(s) {return  s.normalize('NFKC'); });

ただ、多くの場合、ここまでやる必要はないと思われる。

0 件のコメント:

コメントを投稿