Excel XLSXへの簡単なエクスポート

前の記事で始まったトピックの続きで、特にXLSX形式でデータをエクスポートした経験を共有したいと思います。







だから、大規模で複雑なライブラリなしでXLSXを埋める方法を気にする人は、猫の下でください。



最近、予測不可能なサイズの表形式データをXLSX形式でエクスポートするタスクがありました。 賢明なプログラマーのように、彼が最初にしたことは既製のソリューションを手に入れることでした。

すぐにPHPExcelライブラリに出会いました。 さまざまな機能を備えた強力なソリューション。 もう少し騒ぎ立てて、彼女についてのプログラマーのレビューを見つけました。 特に、フォーラムでは、作業の速度と大量のデータを使用することの拒否について苦情があります。 彼は図書館を解決策の1つとして指摘し、さらに調査を始めました。

XLSXを操作するためのライブラリがさらにいくつか見つかりましたが、それらはすべて忘れられていました。 2〜3年更新されなかった、または必然的にサードパーティのライブラリをドラッグしたり、 DOMを使用してファイルを操作したが、これはあまり好きではなかった 毎回、別の図書館にぶつかり、その作業のメカニズムを研究して、私はこれがすべて「スズメの大砲から」であると考えていました。 そんな難しい決断は必要ありません!

正直に言って、見つかった各ソリューションを表面的に研究したので、単一のソリューションのインストールとテストは開始しませんでした。 タンクのような、よりシンプルで信頼性の高いソリューションが必要でした。



挑戦する



一般に、私は適切なものを見つけられなかったため、必要なものの技術的要件を策定する必要があります。 予想どおり、要件は簡単でした。



最後の点だけを説明します。 ご存じのように、XLSXは通常のzipアーカイブであり、解凍して複数のファイルとディレクトリで構成されていることがわかります。 逆の方法で、パッケージ化してXLSXに名前変更できます。 すべての変更が正しい場合、Microsoft Excelは問題なくファイルを開きます。



実装



最初は、XLSXを構成するすべてのファイルをコードで作成したかったのですが、幸いなことに、自分のアイデアの無意味さをすぐに実感しました。 そして、より正確でシンプルな別のソリューションが生まれました。 Microsoft Excelを使用して、最終的に必要な形式でXLSXファイルを作成する必要がありますが、データ、つまりテンプレートはなく、コードを使用してデータを追加するだけです!

この場合、クラスはテンプレートを別のディレクトリに解凍し、/ xl / worksheets / sheet1.xmlに変更を加え、ディレクトリの内容をXLSXに戻す必要があります。



クラス宣言にはパブリック変数が含まれます。

$ templateFile-テンプレートファイルの名前

$ exportDir-テンプレートが展開されるフォルダー。もちろん、必要なアクセス権があります。



クラスのコンストラクターは、将来のファイルの名前、列と行の数を受け入れます。 次に、ファイル名が正しいこと、テンプレートを解凍するためのフォルダーが存在することを確認し、テンプレートを解凍するための宛先フォルダーのフルネームを形成します。

クラスを作成した後、テンプレートを解凍し、記述用にsheet1.xmlを開くことができます。 実際、ファイルに追加するだけでなく、完全に上書きします。 最初の行を取得したら、エクスポートされた範囲のサイズを反映するディメンションタグを変更し、ファイルに書き込みます。



public function openWriter() { if (is_dir($this->baseDir)) CFileHelper::removeDirectory($this->baseDir); mkdir($this->baseDir); exec("unzip $this->templateFullFilename -d \"$this->baseDir\""); $this->workSheetHandler = fopen($this->baseDir.'/xl/worksheets/sheet1.xml', 'w+'); fwrite($this->workSheetHandler, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><dimension ref="A1:'.chr(64+$this->colCount).$this->rowCount.'"/><sheetData>'); }
      
      







大量のデータを処理する速度と機能を確保するために、resetRowおよびflushRow関数が許可されています。 メモリ内の現在の行をクリアし、現在の行をディスクに書き込む責任があります。

しかし、異なるタイプのセルの値を保存することは、それほど簡単な作業ではありませんでした。



行レコード


ファイルに文字列値を書き込むのは難しいようです。 ただし、XLSXはそれほど単純ではありません。 XLSX内のすべての行は、個別のファイル/xl/sharedStrings.xmlに保存されます。 セルに書き込まれるのは文字列値ではなく、シリアル番号(インデックス)です。 ファイルサイズの削減という点でスマートなソリューション。



しかし、このようなソリューションは、テンプレートにソフトウェアを入力するという観点からは不便です。 この要件が満たされている場合、データ配列内のすべての文字列値を個別に通過させ、重複するものを除外し、sharedStrings.xmlに保存し、インデックスを作成して、元の配列の値の代わりにインデックスを入力する必要があります。 遅くて不快。



要件を回避し、セルの文字列値をセルに直接保存できることがわかりました。 ただし、この場合、記録形式は異なります。



 public function appendCellString($value) { $this->curCel++; if (!empty($value)) { $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); $value = preg_replace( '/[\x00-\x13]/', '', $value ); $this->currentRow[] = '<cr="'.chr(64+$this->curCel).$this->numRows.'" t="inlineStr"'.($this->isBold ? ' s="7"' : '').'><is><t>'.$value.'</t></is></c>'; $this->numStrings++; } }
      
      







番号記録


整数または小数の記述に問題はありませんでした。 すべてが簡単です:



 public function appendCellNum($value) { $this->curCel++; $this->currentRow[] = '<cr="'.chr(64+$this->curCel).$this->numRows.'"><v>'.$value.'</v></c>'; }
      
      







日時記録


日付と時刻は、1970年1月1日からの経過秒数を日数の秒数で割った値として保存されます。 さらに、うるう年の定義を使用した計算でエラーが発生しました。 一般に、ネットワーク上で簡単に見つけられる詳細に進むことなく、日付を正しく計算するために、クラスで2つの定数を宣言する必要がありました。

ZERO_TIMESTAMP - UNIX_TIMESTAMPからのExcel形式の日付オフセット

SEC_IN_DAY-秒単位の日。

日付と時刻の値を計算した後、小数部の整数部は日付であり、小数部は時刻です。



 const ZERO_TIMESTAMP = 2209161600; const SEC_IN_DAY = 86400; public function appendCellDateTime($value) { $this->curCel++; if (empty($value)) $this->appendCellString(''); else { $dt = new DateTime($value); $ts = $dt->getTimestamp() + self::ZERO_TIMESTAMP; $this->currentRow[] = '<cr="'.chr(64+$this->curCel).$this->numRows.'" s="1"><v>'.$ts/self::SEC_IN_DAY.'</v></c>'; } }
      
      





すべてのデータを記録した後、ワークシートとブックを閉じるために残ります。



申込み



前述のように、説明したクラスの使用は、 CArrayDataProviderプロバイダーを使用したデータのエクスポートに基づいています。 エクスポートされたデータの量が非常に大きくなる可能性があると仮定すると、返されたデータを100レコードずつ反復する特別なイテレーターCDataProviderIteratorが適用されました (異なる数のレコードを指定できます)。



 public function exportXLSX($organization, $user, &$filename) { $this->_provider = new CArrayDataProvider(/*query*/); Yii::import('ext.AlxdExportXLSX.AlxdExportXLSX'); $export = new AlxdExportXLSX($filename, count($this->_attributes), $this->_provider->getTotalItemCount() + 1); $export->openWriter(); $export->resetRow(); $export->openRow(true); foreach ($this->_attributes as $code => $format) $export->appendCellString($this->_objectref->getAttributeLabel($code)); $export->closeRow(); $export->flushRow(); $rows = new CDataProviderIterator($this->_provider, 100); foreach ($rows as $row) { $export->resetRow(); $export->openRow(); foreach ($this->_attributes as $code => $format) { switch ($format->type) { case 'Num': $export->appendCellNum($row[$code]); /*other types*/ default: $export->appendCellString(''); } } $export->closeRow(); $export->flushRow(); } $export->closeWriter(); $export->zip(); $filename = $export->getZipFullFileName(); }
      
      





興味がある人は誰でも私のAlxdExportXLSXクラスのソースコードを無料で入手できます。



All Articles