質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Q&A

解決済

1回答

13493閲覧

phpspreadsheetでシートコピーすると、出来上がったファイルが破損している

makotokw

総合スコア24

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

0グッド

1クリップ

投稿2020/07/09 21:16

編集2020/07/09 22:40

前提・実現したいこと

ひな形となるシートをコピーし、新しくコピーしたシート側に各種データを埋め込む機能を実装中に以下のエラーメッセージが発生しました。

生成されたExcelファイルが破損してしまいます。


何が問題なのかが理解できず困っております
ご教示いただけますでしょうか、よろしくお願いします


【環境】

Linux側Windows側
CentOS7 + PHP7.3.19 + phpspreadsheet(1.13.0)Windows10 + Excel2019

発生している問題・エラーメッセージ

phpで生成されたExcelファイルを開くと下記のメッセージが表示されます。

エラーメッセージ

該当のソースコード

<?php require '../phpLibs/PhpSpreadsheet/vendor/autoload.php'; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter; use PhpOffice\PhpSpreadsheet\Reader\Xls as XlsReader; use PhpOffice\PhpSpreadsheet\Writer\Xls as XlsWriter; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Fill; use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Style\Conditional; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Document\Properties; require '../phpLibs/Ginq/src/Ginq.php'; require '../config.php'; $reader = new XlsxReader(); $spreadsheet = $reader->load('../tmp/新規 Microsoft Excel ワークシート.xlsx'); $spreadsheet->setActiveSheetIndex(0); $mastersheet = $spreadsheet->getSheetByName('Sheet1'); $worksheet = clone $mastersheet; $worksheet->setTitle('シート1'); $spreadsheet->addSheet($worksheet); $worksheet->setCellValue('A1','かきこみ'); $writer = new XlsxWriter($spreadsheet); $writer->save('../tmp/TEST_generate.xlsx');

試したこと

  • Windows10で右クリックして「新規作成」→「Microsoft Excel ワークシート」の操作を行って

新規 Microsoft Excel ワークシート.xlsx を作成し、何も変更していないファイルを
テンプレートとして使用し、上記プログラムを実行。
ファイル破損のメッセージは表示されない。

  • Windows10で右クリックして「新規作成」→「Microsoft Excel ワークシート」の操作を行って

新規 Microsoft Excel ワークシート.xlsx を作成し、適当なセルに値を入力して保存。
保存したファイルをテンプレートとして使用し、上記プログラムを実行。
ファイル破損のメッセージが表示される。

  • .xlsxではなく、.xlsファイルをテンプレートとして使用、上記プログラムを実行。

ファイル破損のメッセージが表示される。

  • テンプレートとなるファイル名を TEST.xlsxに変更して、上記プログラムを実行。

ファイル破損のメッセージが表示される。

  • セルに値を書き込む箇所をコメントにし、シートをコピーするだけでも破損していました。

// $worksheet->setCellValue('A1','かきこみ');
ファイル破損のメッセージが表示される。

補足情報(FW/ツールのバージョンなど)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2020/07/09 21:58

>の一ぐの内容に問題が見つかりました。 コピペできないメッセージでしたら、画面キャプチャで良いですよ。
m.ts10806

2020/07/09 21:58

>$spreadsheet = $reader->load('../tmp/新規 Microsoft Excel ワークシート.xlsx'); スペースや全角文字なしで半角英数のみのファイル名にした場合はどうでしょうか。
makotokw

2020/07/09 22:23

> コピペできないメッセージでしたら、画面キャプチャで良いですよ。 誤字ありました。画面キャプチャでいいんですね。 承知しました。 > スペースや全角文字なしで半角英数のみのファイル名にした場合はどうでしょうか 入力元となるテンプレートファイルを,TEST.xlsxにして実行しても状況は変わらなかったです。
makotokw

2020/07/09 22:32

セルに値を書き込む箇所をコメントにし、シートをコピーするだけでも破損していました。 // $worksheet->setCellValue('A1','かきこみ');
makotokw

2020/07/09 22:58

IOFactoryを使用するように修正して実行したのですが、変化なしです。 いろいろと調べて下さってうれしいです。 //$spreadsheet = $reader->load('../tmp/TEST.xlsx'); $spreadsheet = IOFactory::load('../tmp/TEST.xlsx'); : : //$writer = new XlsxWriter($spreadsheet); $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); $writer->save('../tmp/TEST_generate.xlsx');
m.ts10806

2020/07/09 23:43

一応、die()(またはexit())も試してみてください。 私もだいぶ昔ですが、exit()で締めることにより解決した経験はあります。
m.ts10806

2020/07/09 23:44

あ、hentaimanさんの提示されているエラーハンドリングは必須ですね。 ファイル操作なので。(JavaだとIOException書かないと怒られます)
makotokw

2020/07/10 00:00

try ~ catch で囲って実行したのですが、何も例外は発生しませんでした。 > 一応、die()(またはexit())も試してみてください。 ブラウザに直接バイナリをレスポンスとして返すなら、exitを指定した方がいいとの記載も見つけて 既にやっていたのですが、状況は同じでした。 本当に原因不明で困りもんです...
hentaiman

2020/07/10 00:03

そうなると後はテキストエディタで開いて余計な文字やエラーが出力されてないかを確認するしかないな xlsxはxmlなのでテキストエディタで開けるので
m.ts10806

2020/07/10 00:04

CentOS上でなかったらどうなんだろうと気にはなっています。 つまり、きちんとExcelが入っている環境だとどうか。
makotokw

2020/07/10 00:32

作成されたxlsxをバイナリで見たところ、先頭がPKで始まっていてZIPファイル圧縮されている感じだったので、TEST_generate.xlsx.zip にリネームし、ZIPファイルを解凍してみたけど、余分な文字とかエラは出力されていませんでした。
makotokw

2020/07/10 00:45

Linux上のphpspreadsheetフォルダと、テストプログラムをWindows上に持ってきて、同様のテストを 行ったところ、破損のエラーが発生しませんでした。
hentaiman

2020/07/10 01:02

ということはもしかして $worksheet->setTitle('シート1'); を $worksheet->setTitle('sheet999'); にしたら破損しなくならないか?
makotokw

2020/07/10 01:08

$worksheet->setTitle('シート1');             ↓ $worksheet->setTitle('sheet999'); 試してみましたが、ダメでした...
hentaiman

2020/07/10 01:11

$worksheet->setCellValue('A1','かきこみ') の処理無しでもダメでしたか?
makotokw

2020/07/10 11:52

原因追及までしていませんが、なんとなく状況は把握できました。 phpspreadsheet+Excel2019 の組み合わせが悪いみたいです...
guest

回答1

0

自己解決

詳細な状況がわかってきました。
テンプレートとなるExcelファイルのシートにプリンタ設定情報が含まている場合、
そのシートをコピーすると、コピーされた新しいシートにも同じプリンタ設定情報がコピーされます。

phpspreadsheetで、コピーしたシートのプリンタ設定情報の扱いがバグっている感じです。
イメージ説明

【確認方法】
1.生成後エラーが出るxlsxファイルの拡張子をzipに変更。
2.エクスプローラーでzipファイルをダブルクリック。
3.フォルダ移動。 xl\printerSettings
※ここのフォルダに同じ名前の printerSettings1.bin がコピーしたシート数分あります。


phpspreadsheet自体を修正する技術量はないので、暫定対応で回避する。

【暫定対応】
1.phpspreadsheetで生成されたxlsxファイルをunzipで解凍し、重複ファイルを1つにする。
2.解凍されたファイルをzipで再圧縮する。 ※拡張子は .xlsx で圧縮

php

1require '../phpLibs/PhpSpreadsheet/vendor/autoload.php'; 2 3use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; 4use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter; 5use PhpOffice\PhpSpreadsheet\IOFactory; 6 7 8// Excel2016の環境で、右クリック⇒新規作成⇒Microsoft Excel ワークシート 9copyTemplateSheet('../tmp/Excel2016_Rclick_NEW.xlsx', 10 '../tmp/Excel2016_Rclick_NEW_generate.xlsx'); 11 12// Excel2016の環境で、右クリック⇒新規作成⇒Microsoft Excel ワークシート ※新規作成後、ファイルを開いて内容を更新して保存 13copyTemplateSheet('../tmp/Excel2016_Rclick_NEW_Save.xlsx', 14 '../tmp/Excel2016_Rclick_NEW_Save_generate.xlsx'); 15 16// Excel2016の環境で、右クリック⇒新規作成⇒Microsoft Excel ワークシート ※新規作成後、ファイルを開いて内容を更新して保存(プリンタ設定を変更) 17copyTemplateSheet('../tmp/Excel2016_Rclick_NEW_Save2.xlsx', 18 '../tmp/Excel2016_Rclick_NEW_Save2_generate.xlsx'); 19 20// Excel2019の環境で、右クリック⇒新規作成⇒Microsoft Excel ワークシート 21copyTemplateSheet('../tmp/Excel2019_Rclick_NEW.xlsx', 22 '../tmp/Excel2019_Rclick_NEW_generate.xlsx'); 23 24// Excel2019の環境で、右クリック⇒新規作成⇒Microsoft Excel ワークシート ※新規作成後、ファイルを開いて内容を更新して保存 25copyTemplateSheet('../tmp/Excel2019_Rclick_NEW_Save.xlsx', 26 '../tmp/Excel2019_Rclick_NEW_Save_generate.xlsx'); 27 28exit; 29 30function copyTemplateSheet($load_filename, $save_filename) { 31 $save_folder = realpath(pathinfo($save_filename, PATHINFO_DIRNAME)); 32 $save_basename = pathinfo($save_filename, PATHINFO_BASENAME ); 33 34 try { 35 $reader = new XlsxReader(); 36 $spreadsheet = $reader->load($load_filename); 37 $spreadsheet->setActiveSheetIndex(0); 38 39 $mastersheet = $spreadsheet->getSheetByName('Sheet1'); 40 41 $worksheet = clone $mastersheet; 42 $worksheet->setTitle('シート1'); 43 $spreadsheet->addSheet($worksheet); 44 45 $worksheet->setCellValue('A1','かきこみ'); 46 $writer = new XlsxWriter($spreadsheet); 47 $writer->save($save_filename); 48 49 // 解凍先のtmpフォルダ作成 50 if(file_exists($save_filename.'.tmp')) { 51 removeDirectory($save_filename.'.tmp'); 52 } 53 mkdir($save_filename.'.tmp', 0777, true); 54 55 //tmpフォルダにxlsxファイルを解凍 ※xlsxファイルはzip圧縮されている? 56 //この段階で、重複しているファイルが1つにまとめられる 57 shell_exec('unzip -o '.$save_filename.' -d '.$save_filename.'.tmp'); 58 59 // 生成されたxlsxファイルを削除 60 unlink($save_filename); 61 62 // tmpフォルダの内容で再圧縮し、xlsxファイルを作成する 63 $cwd = getcwd(); 64 chdir($save_filename.'.tmp'); 65 shell_exec('zip -r '.$save_folder.'/'.$save_basename.' *'); 66 chdir($cwd); 67 68 // tmpフォルダを削除 69 removeDirectory($save_filename.'.tmp'); 70 71 } catch(Exception $e) { 72 die('Error: '.$e->getMessage()); 73 } 74} 75 76function removeDirectory($directory) { 77 $result = true; 78 foreach(new \RecursiveIteratorIterator( 79 new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::SKIP_DOTS), 80 \RecursiveIteratorIterator::CHILD_FIRST 81 ) as $file) { 82 if ($file->isDir()) { 83 $result &= @rmdir($file->getPathname()); 84 } else { 85 $result &= @unlink($file->getPathname()); 86 } 87 } 88 return $result && @rmdir($directory); 89}

【根本的な原因】
phpspreadsheetの内部で使用している、zipstream-phpというライブラリにバグがある。
https://github.com/maennchen/ZipStream-PHP/issues/154

xlsxファイルを解凍後に再圧縮するのではなく、phpspreadsheetを暫定修正する方法も考えてみた。

【phpspreadsheetを暫定的に修正】
既にZipファイルに追加されているファイルは複数回追加されないようにチェック。
vendor\phpoffice\phpspreadsheet\src\PhpSpreadsheet\Writer\Xlsx.php

php

1 2public function save($pFilename) 3     : 4     : 5 6280行目付近》 7 8 // Add worksheet relationships (drawings, ...) 9 $added_files = array(); // ※追加 10 for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { 11 // Add relationships 12 $zip->addFile('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts)); 13 14 // Add unparsedLoadedData 15 $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName(); 16 $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData(); 17 if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) { 18 foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) { 19 $zip->addFile($ctrlProp['filePath'], $ctrlProp['content']); 20 } 21 } 22 if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) { 23 foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) { 24 if(!isset($added_files[$ctrlProp['filePath']])) { // ※追加 25 $zip->addFile($ctrlProp['filePath'], $ctrlProp['content']); 26 $added_files[$ctrlProp['filePath']] = $ctrlProp['filePath']; // ※追加 27 } // ※追加 28 } 29 } 30 31 32

投稿2020/07/10 11:49

編集2020/07/11 00:29
makotokw

総合スコア24

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2020/07/10 11:53

365シリーズだとどうなるか見ものですね。 ただやはり、PHPはファイル操作には不向きな言語と言えそうです。
hentaiman

2020/07/10 16:25 編集

なるほどーこれは他者にも有益な自己解決だ
makotokw

2020/07/10 22:53

Excelのバージョンは関係なく、テンプレートとなるExcelファイルにプリンタ設定情報が格納されているかどうかで動きが変わるかんじです。 プログラムで暫定対応して回避しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問