Skip to content

Instantly share code, notes, and snippets.

@Keirodev
Forked from ihumanable/Excel.php
Last active August 3, 2016 17:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Keirodev/dbbd8c853d1c09af6ccb to your computer and use it in GitHub Desktop.
Save Keirodev/dbbd8c853d1c09af6ccb to your computer and use it in GitHub Desktop.
Simple Excel Writer in PHP
<?php
namespace Exaprint\ExaRequete\Models;
/**
* Simple excel writer class with no external dependencies. Handle special caracters as :
* Array('Добрый день', 'Bonne journée','tschüß', 'こんにちは。')
* @author Kévin Monteiro, inspired by Matt Nowack
* @link https://gist.github.com/ihumanable
* @link https://gist.github.com/Keirodev
* @license Unlicensed
* @version 2.0
*/
/**
* Class ExcelModel
* @package Exaprint\ExaRequete\Models
*/
class ExcelModel
{
private $col;
private $row;
private $data;
private $title;
/*
* Strings that define start & end of an excel file
*/
private $bofPack;
private $eofPack;
/**
* Builds a new Excel Spreadsheet object
* @param $title : Name of the file to download
*/
public function __construct($title)
{
//$this->bofPack = pack('ssssss', 0x809, 0x8, 0x0, 0x10, 0x0, 0x0);
$this->bofPack = pack('s4', 0x809, 0x0004, 0x0600, 0x10);
//$this->eofPack = pack('ss', 0x0A, 0x00);
$this->eofPack = pack('ss', 0x0A, 0x00);
$this->title = $title;
$this->col = 0;
$this->row = 0;
$this->data = '';
$this->bofMarker();
}
/**
* Writes the Excel Beginning of File marker
* @see pack()
*/
public function bofMarker()
{
$this->data .= $this->bofPack;
}
/**
* Send the xls build or false if it integration failed
* @throws \ErrorException
*/
public function send()
{
$this->eofMarker();
$this->headers();
//return the xls if validated, false otherwise
if ($this->checkIntegrity()) {
echo $this->data;
} else {
throw new \ErrorException('XLS integrity invalidated');
}
}
/**
* Writes the Excel End of File marker
* @see pack()
*/
public function eofMarker()
{
$this->data .= $this->eofPack;
}
/**
* Transmits the proper headers to cause a download to occur and to identify the file properly
*/
public function headers()
{
header('Content-Type: application/force-download');
header('Content-Type: application/octet-stream');
header('Content-Type: application/download');
header('Content-Disposition: attachment;filename=' . ExcelModel::filename($this->title) . '.xls ');
header('Content-Transfer-Encoding: binary ');
}
/**
* Safely encode a string for use as a filename
* @param string $title The title to use for the file
* @return string The file safe title
*/
public static function filename($title)
{
$result = strtolower(trim($title));
$result = str_replace("'", '', $result);
$result = preg_replace('#[^a-z0-9_]+#', '-', $result);
$result = preg_replace('#\-{2,}#', '-', $result);
return preg_replace('#(^\-+|\-+$)#D', '', $result);
}
/**
* Check integrity of xls build based on binary values on the beginning and eof
* @return bool
*/
public function checkIntegrity()
{
$start = $this->bofPack;
$end = $this->eofPack;
$startLength = strlen($start);
$endLength = strlen($end);
$xls = $this->getData();
return ($start === substr($xls, 0, $startLength) && $end === substr($xls, -$endLength));
}
/**
* public for testing purpose
* @return string = xls
*/
public function getData()
{
return $this->data;
}
/**
* Moves internal cursor left by the amount specified
* @param $amount : optional integer. The amount to move left by, defaults to 1
* @return integer : The current column after the move
*/
public function left($amount = 1)
{
$this->col -= $amount;
if ($this->col < 0) {
$this->col = 0;
}
return $this->col;
}
/**
* Moves internal cursor right by the amount specified
* @param $amount : optionnal integer. The amount to move right by, defaults to 1
* @return integer The current column after the move
*/
public function right($amount = 1)
{
$this->col += $amount;
return $this->col;
}
/**
* Moves internal cursor up by amount
* @param $amount : optional integer The amount to move up by, defaults to 1
* @return integer The current row after the move
*/
public function up($amount = 1)
{
$this->row -= $amount;
if ($this->row < 0) {
$this->row = 0;
}
return $this->row;
}
/**
* Start a new line
*/
public function newLine()
{
$this->down();
$this->home();
}
/**
* Moves internal cursor down by amount
* @param $amount : optional integer The amount to move down by, defaults to 1
* @return integer The current row after the move
*/
public function down($amount = 1)
{
$this->row += $amount;
return $this->row;
}
/**
* Moves internal cursor all the way left, col = 0
*/
public function home()
{
$this->col = 0;
}
/**
* Start top left of this sheet
*/
public function start()
{
$this->top();
$this->home();
}
/**
* Moves internal cursor to the top of the page, row = 0
*/
public function top()
{
$this->row = 0;
}
/**
* Writes a string (or label) to the Excel Spreadsheet
* @see pack()
* @param string $value The value to write out
*/
public function label($value)
{
// care about accent
$value = mb_convert_encoding($value, 'UTF-16LE', 'UTF-8');
$length = mb_strlen($value, 'UTF-16LE');
$this->data .= pack('ssssssC', 0x204, 9 + 2 * $length, $this->row, $this->col, 0x0, $length, 0x1);
$this->data .= $value;
}
/**
* Writes a number to the Excel Spreadsheet
* @see pack()
* @param integer $value The value to write out
*/
public function number($value)
{
$this->data .= pack('sssss', 0x203, 14, $this->row, $this->col, 0x0);
$this->data .= pack('d', $value);
}
}
/*
Exemple for testing
public function testCreateXlsFile()
{
$table = Array(
Array('Добрый день', 'Bonne journée'),
Array('tschüß', 'こんにちは。'),
Array(30, 40));
$xls = self::array_to_excel($table);
$file = 'data/basic_xls.xls';
if (!file_exists($file)) {
@mkdir('data', 0777);
}
file_put_contents($file, $xls);
self::assertFileExists($file);
}
private static function array_to_excel($input)
{
$xls = new ExcelModel('array_testing');
$xls->start();
foreach (array_values($input) as $lineNumber => $row) {
foreach (array_values($row) as $colNumber => $data) {
if (is_numeric($data)) {
$xls->number($data);
} else {
$xls->label($data);
}
$xls->right();
}
$xls->newLine();
}
$xls->eofMarker();
return $xls->getData();
// or replace thoses 2 lines above by $xls->send() for browser download.
}
*/
@sbsbessa
Copy link

sbsbessa commented Aug 3, 2016

@Keirodev we're using this to generate excel files and we can successfully open them with LibreOffice but MSOffice says the file is corrupt, do you have any hint on this? Thanks, Sérgio

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment