CSV形式ファイルを読み込んで表にする(その2)

(1/1)
PHPでCSV形式ファイルを読み込んで表にする」では、関数  fgetcsv  を利用し、CSV形式ファイルをHTMLのTABLEタグに変換するプログラムを紹介した。
しかし、関数fgetcsvは、CSVファイル中の空白文字を無視する。空白文字を含む場合は、フィールド囲い文字を利用し、"_空白文字がある" のようにするのがCSVの定石なので、このような動作になっているものと思われる。
そこで今回は、フィールド区切り文字が無くとも空白文字を扱うことができ、さらにデリミタやフィールド囲い文字を正規表現で指定できるオリジナルのfgetcsvをつくってみることにする。

(2023年4月29日)囲い文字の中にデリミタがある場合の処理を追加

サンプル・プログラムの実行例

PHPでCSV形式ファイルを読み込んで表にする(その2)

目次

サンプル・プログラムのダウンロード

圧縮ファイルの内容
csv2table2.phpサンプル・プログラム本体。
sample2.csvサンプルCSVファイル。
csv2table2.php 更新履歴
バージョン 更新日 内容
3.1.0 2023/04/29 囲い文字の中にデリミタがある場合の処理を追加
3.0 2021/05/29 PHP8対応
2.1 2009/01/24 空白を に
2.0 2008/07/26 初版

解説:空白文字に対応した fgetcsv

PHPの関数定義では、デフォルト引数値を利用することができる。第一引数のみ必須で、第二引数以降はオプション(省略可能)であるようなユーザー関数 myfgetcsv を定義できる。

CSVファイルの読み込みは、関数  fgets  を利用している。
読み取りに失敗したり、文字列長がゼロの場合(fgetsは改行文字まで読み込むので、空行でも文字列長がゼロになることはない)、ファイルの終端に達したものと考える。

次に、空白文字を残し、行末の改行文字のみを削除するために関数  rtrim  を利用する。

CSVを個々のフィールドに分解するのに、関数  preg_split  を利用した。
正規表現を用いてフィールドを分解することができる

次に、フィールド囲い文字を削除するために関数  preg_replace  を利用した。

 125: /**
 126:  * 空白文字に対応した fgetcsv関数.
 127:  * デリミタやフィールド囲い文字,エンコードを指定できる.
 128:  * @param   object $handle ファイルポインタ
 129:  * @param   int    $length 1行の最大長(省略可)
 130:  * @param   string $delimiter フィールドのデリミタ;正規表現指定 (省略時は ,)
 131:  * @param   string $enclosure フィールド囲い文字;正規表現指定(省略時は ")
 132:  * @param   string $encode    ファイルのエンコード(省略時はauto)
 133:  * @return  array CSVフィールド
 134: */
 135: function myfgetcsv($handle, $length=0, $delimiter='/,/ui', $enclosure='"', $encode='auto') {
 136:     //1行の最大長を考慮してファイル読み込みする.
 137:     $s = ($length > 0? fgets($handle, $length: fgets($handle);
 138:     if ($s == FALSE || $s == '')    return FALSE;
 139:     //エンコード変換
 140:     $s = mb_convert_encoding($s, INTERNAL_ENCODING, 'auto');
 141:     //1行の右側の空白を削除する.
 142:     $s = rtrim($s);
 143:     //カラムに分解する.
 144:     $arr1 = preg_split($delimiter, $s);
 145:     $arr2 = array();
 146:     $arr3 = array();
 147: 
 148:     //カラムを1個ずつ処理する
 149:     $offset = 0;
 150:     $flag = FALSE;
 151:     foreach ($arr1 as $key=>$val) {
 152:         $pat1 = '/^\\' . $enclosure . '([^\\' . $enclosure . ']+)\\' . $enclosure . '$/ui';
 153:         $pat2 = '/^\\' . $enclosure . '([^\\' . $enclosure . ']*)$/ui';
 154:         $pat3 = '/^([^\\' . $enclosure . ']*)\\' . $enclosure . '$/ui';
 155:         //囲い文字に囲まれている場合
 156:         if (preg_match($pat1, $val, $arr3> 0) {
 157:             $arr2[$offset] = $arr3[1];
 158:             $offset++;
 159:         //囲い文字の中にデリミタがある場合(開始処理)
 160:         } else if (preg_match($pat2, $val, $arr3> 0) {
 161:             $arr2[$offset] = $arr3[1];
 162:             $flag = TRUE;
 163:         } else if ($flag) {
 164:             //囲い文字の中にデリミタがある場合(終了処理)
 165:             if (preg_match($pat3, $val, $arr3> 0) {
 166:                 $arr2[$offset.$arr3[1];
 167:                 $flag = FALSE;
 168:                 $offset++;
 169:             } else {
 170:                 $arr2[$offset.$val;
 171:             }
 172:         //囲まれていない場合
 173:         } else {
 174:             $arr2[$offset] = $val;
 175:             $offset++;
 176:         }
 177:     }
 178:     return $arr2;
 179: }

解説:CSVを変換

CSVファイルをTABLEタグに変換するユーザー関数が csv2table である。

 htmlentities  を用い、HTMLタグやPHPスクリプトとして解釈される可能性がある特殊文字をHTMLエンティティ(< >など)に変換してから表示する。
そのまま表示してしまうと、スクリプトとして実行され、システムの弱点を突かれる恐れがあるからである。

 181: /**
 182:  * CSV形式ファイル名を指定し,TABLEタグ文字列を求める.
 183:  * エラーが発生したときには$errmsgに代入する.
 184:  * @param   string $fname  CSVファイル名
 185:  * @param   string $errmsg エラーメッセージ格納用
 186:  * @return  string TABLEタグ文字列
 187: */
 188: function csv2table($fname, &$errmsg) {
 189:     $html =<<< EOT
 190: <table class="plists">
 191: 
 192: EOT;
 193: 
 194:     //CSV形式ファイルを読み込みオープンする.
 195:     if (($infp  = fopen($fname, 'r')) == FALSE) {
 196:         $errmsg = 'サーバ・トラブルが発生しました';
 197:         return FALSE;
 198:     }
 199:     //CSV形式ファイルを1行ずつ読み込む.
 200:     while (($csv = myfgetcsv($infp, MAX_LINE)) !== FALSE) {
 201:         $html ."<tr>\n";
 202:         foreach ($csv as $key=>$val) {
 203:             //特殊文字をHTMLエンティティに変換する.
 204:             $val = htmlentities($val, ENT_QUOTES, INTERNAL_ENCODING);
 205:             //デリミタ間にデータが存在しない場合は空白文字にする.
 206:             if ($val == '') {
 207:                 $val ='&nbsp;';
 208:             }
 209:             //空白を&nbsp;に変換する.
 210:             $val = preg_replace('/ /u', '&nbsp;', $val);
 211:             //カラム1つ分のHTMLタグを追記する.
 212:             $html ."<td>{$val}</td>\n";
 213:         }
 214:         //1行の最後
 215:         $html ."</tr>\n";
 216:     }
 217:     //表の最後
 218:     $html .="</table>";
 219: 
 220:     fclose($infp);
 221: 
 222:     return $html;
 223: }

参考サイト

PHPでCSV形式ファイルを読み込んで表にする:ぱふぅ家のホームページ
(この項おわり)
header