Help us understand the problem. What is going on with this article?

【PHP】半角カナで固定長データを作るときの落とし穴

ダンプファイルを作成しようとして固定長データを取得する際に、半角カナ混じりのデータだと引っかかりがちな落とし穴があります。

PHP
   $address = "オオサカシスミノエク";
   $address2 = "サッポロシアツベツク";

まずは、この2つの変数の文字数を調べてみましょう。文字数を調べる時はmb_strlenを使います。

PHP
   echo mb_strlen($address); //10と表示されます
   echo mb_strlen($address2); //濁点、半濁点ともに1文字となるので、12と表示されます。 

では、それを踏まえた上で、このデータを40バイトの固定長で値を作成してみます(今回はわかりやすく、空白部分を*で埋めてみます)。固定長データを作る際には便利なstr_padという関数があるのでこれを使ってみます。

すると…

PHP
   echo str_pad($address,40,"*"); // オオサカシスミノエク*********** と表示される。
   echo str_pad($address2,40,"*"); //サッポロシアツベツク**** と表示される。

想定していたデータは、オオサカシスミノエク******************************のはずなのに、明らかに記号が足りません。サッポロシアツベツクの場合だと、もっと記号が少なくなっています。

落とし穴1:UTF-8の半角カナは3バイト

では、バイト数を取得できるstrlen関数を使って調べてみます。

PHP
   echo strlen($address); //30と表示されます。
   echo strlen($address2); //36と表示されます。

バイト数が、文字数の3倍になっているのがわかります。このように、まず注意しなければいけないのは、UTF-8において、半角カナは1文字あたり3バイトであること、そしてstr_pad関数の引数はバイト数で換算しないといけない、ということです。よって、UTF-8の場合は、半角1文字あたり3バイトなので10*3の30バイト分消費しているため、残りの記号は10文字分しかパディングされないのです。

そのため、str_pad関数を使う場合は、固定長の値も調節が必要になります。半角カナを使うたびに、3バイト消費、言い換えれば2バイト余分に消費しているので、2*半角カナの文字数分、加算しておきます。

PHP
   echo str_pad($address,40 + mb_strlen($address)*2,"*"); //オオサカシスミノエク******************************
   echo str_pad($address2,40 + mb_strlen($address2)*2,"*") //サッポロシアツベツク****************************

このように、半角カナ数の2倍分だけ固定長データを加算しておけば、動的な値の変化にも対応できます。

※なお、シフトJISの場合は2バイトになりますので、半角カナの文字分だけ加算してください。

落とし穴2:半角スペース混じりの半角カナ

では、先程の変数をこのようにしてみます。

PHP
   $address = "オオサカシ スミノエク";
   $address2 = "サッポロ シ アツベツ ク";

この2つの変数の文字数を調べてみましょう。スペースが増えた分だけ増えているはずです。

PHP
   echo mb_strlen($address); //11と表示されます
   echo mb_strlen($address2); //15と表示されます。 

では、同じようにこのデータを40バイトの固定長で値を作成してみます

すると…

PHP
   echo str_pad($address,40,"*"); // オオサカシ スミノエク********* と表示される。
   echo str_pad($address2,40,"*"); // サッポロ シ アツベツ ク* と表示される。

先程と比べるとなにか不可解な結果になっています。

では、バイト数を取得できるstrlen関数を使って調べてみます。

PHP
   echo strlen($address); //31と表示されます。
   echo strlen($address2); //39と表示されます。

おわかりでしょうか?半角カナは3バイトなのに対し、半角スペースは1バイトのままです(当たり前ですが)。したがって、先ほどと同じように固定長データを作ろうとすると、想定外の現象に思わずでくわします。

PHP
   str_pad($address,40 + mb_strlen($address)*2,"*"); //62バイトの想定に対し、61バイトとなる
   str_pad($address,40 + mb_strlen($address2)*2,"*"); //70バイトの想定に対し、67バイトとなる

下手に、半角スペースも含めて半角カナ全部を加算してしまうとこうなってしまい、想定していた固定長が不足してしまいます。したがって、半角スペースも含む場合で固定長バイト数を追加する場合は、半角カナの文字数だけを加算しないといけないので、一度、半角スペースを除外した状態で文字数をカウントする必要があります。

PHP
   str_pad($address,40 + mb_strlen(str_replace(" ","",$address))*2,"*"); //40文字62バイト       
   str_pad($address,40 + mb_strlen(str_replace(" ","",$address2))*2,"*"); //40文字70バイト

これで、正しく固定長データを取得することができました。

参考にしたページ

落とし穴3:mb_convert_kanaで起きる怪現象

PHPにはmb_convert_kanaという、全角カナを半角カナにしてくれたりする、文字操作に非常に便利な関数があるのですが、これには大きな問題点を抱えており、濁点混じりの文字を処理したときに、以下のようになっている場合があります(自分が起きたケースではExcelをPhpSpreadsheetで半角文字を読み込んだ際に起きた現象です)。

PHP
$address = mb_convert_kana($address,"ks");
$address = "ナゴ ヤシテンパ クク"; //濁点や半濁点の処理時に半角スペースが空いた!

これをこのまま処理しようとすると当然バイト数が狂ってしまうので、正しい固定長バイトを取得できません。なので、この場合は元の文字列も含めて濁点と半濁点に対し、置換処理(濁点、半濁点直後のスペース1個分を削除)を行わないといけなくなります。

PHP
   $address = mb_convert_kana($address,"ks");
   $address = "ナゴ ヤシテンパ クク"; //濁点や半濁点の処理時に半角スペースが空いた!
   str_pad($address,40 + mb_strlen(str_replace(" ","",$address))*2,"*"); //40文字62バイト       
   str_pad(str_replace(["゙ ","゚ "],["゙","゚"],$address),40 + mb_strlen(str_replace(" ","",str_replace(["゙ ","゚ "],["゙","゚"],$address)))*2,"*"); //元の文字に対しても処理が必要なので注意

落とし穴4:拗音、促音の小文字に注意

拗音とはキョウトシの、促音とはサッポロシのといった文字の小文字部分です。これを処理するときには固定長ファイルの仕様を再確認する必要もあると思いますが、キウトシではなくキウトシとしなければならない場合があります(特に金融機関情報とか)。その場合は、str_replace関数を使い、力技でねじ伏せるしかありません。

PHP
//このような変換用関数を用意して対応します。
$address = "キョウトシカミギョウク";
$address = mb_convert_kana($address,"ks");
echo $address; // キョウトシカミギョウクと表示される
//拗音を変更する
$address = str_replace(["ァ","ィ","ゥ","ェ","ォ","ッ","ャ","ュ","ョ"],["ア","イ","ウ","エ","オ","ツ","ヤ","ユ","ヨ"],$address);
echo $address; //キヨウトシカミギヨウクと表示される
BRSF
職業、PG・SE・DBエンジニア。オープン環境のwebプログラムをメインにシステム構築担当。使用言語はPHP(cakePHP、Laravel含)jQuery、JavaScript、ExcelVBA、Perl、Ruby、Python。現在Vue、React、Angular強化中。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした