diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index 6445f79..f91a063 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -28,11 +28,7 @@ class CheckinCsvImporter public function execute() { // Guess charset - $head = file_get_contents($this->filename, false, null, 0, 1024); - $charset = mb_detect_encoding($head, ['ASCII', 'UTF-8', 'SJIS-win'], true); - if (array_search($charset, ['UTF-8', 'SJIS-win'], true) === false) { - throw new CsvImportException(['文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。']); - } + $charset = $this->guessCharset($this->filename); // Open CSV $csv = Reader::createFromPath($this->filename, 'r'); @@ -101,4 +97,51 @@ class CheckinCsvImporter } }); } + + /** + * 指定されたファイルを読み込み、文字コードの判定を行います。 + * @param string $filename CSVファイル名 + * @param int $samplingLength ファイルの先頭から何バイトを判定に使用するかを指定 + * @return string 検出した文字コード (UTF-8, SJIS-win, ...) + * @throws CsvImportException ファイルの読み込みに失敗した、文字コードを判定できなかった、または非対応文字コードを検出した場合にスロー + */ + private function guessCharset(string $filename, int $samplingLength = 1024): string + { + $fp = fopen($filename, 'rb'); + if (!$fp) { + throw new CsvImportException(['CSVファイルの読み込み中にエラーが発生しました。']); + } + + try { + $head = fread($fp, $samplingLength); + if ($head === false) { + throw new CsvImportException(['CSVファイルの読み込み中にエラーが発生しました。']); + } + + for ($addition = 0; $addition < 4; $addition++) { + $charset = mb_detect_encoding($head, ['ASCII', 'UTF-8', 'SJIS-win'], true); + if ($charset) { + if (array_search($charset, ['UTF-8', 'SJIS-win'], true) === false) { + throw new CsvImportException(['文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。']); + } else { + return $charset; + } + } + + // 1バイト追加で読み込んだら、文字境界に到達して上手く判定できるかもしれない + if (feof($fp)) { + break; + } + $next = fread($fp, 1); + if ($next === false) { + throw new CsvImportException(['CSVファイルの読み込み中にエラーが発生しました。']); + } + $head .= $next; + } + + throw new CsvImportException(['文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。']); + } finally { + fclose($fp); + } + } }