235 lines
7.2 KiB
PHP
235 lines
7.2 KiB
PHP
<?php
|
|
/**
|
|
* MS-Excel stream handler
|
|
* This class read/writes a data stream directly
|
|
* from/to a Microsoft Excel spreadsheet
|
|
* opened with the xlsfile:// protocol
|
|
* This is used to export associative array data directly to MS-Excel
|
|
* @requires PHP 4 >= 4.3.2
|
|
* @author Ignatius Teo <ignatius@act28.com>
|
|
* @copyright (C)2004 act28.com <http://act28.com>
|
|
* @version 0.3
|
|
* @date 20 Jan 2005
|
|
* $Id: excel.php,v 1.3 2005/01/20 09:58:58 Owner Exp $
|
|
*/
|
|
class xlsStream
|
|
{
|
|
/* private */
|
|
var $position = 0; // stream pointer
|
|
var $mode = "rb"; // default stream open mode
|
|
var $xlsfilename = null; // stream name
|
|
var $fp = null; // internal stream pointer to physical file
|
|
var $buffer = null; // internal write buffer
|
|
var $endian = "unknown"; // little | unknown | big endian mode
|
|
var $bin = array(
|
|
"big" => "v",
|
|
"little" => "s",
|
|
"unknown" => "s",
|
|
);
|
|
|
|
/**
|
|
* detect server endian mode
|
|
* thanks to Charles Turner for picking this one up
|
|
* @access private
|
|
* @params void
|
|
* @returns void
|
|
* @see http://www.phpdig.net/ref/rn45re877.html
|
|
*/
|
|
function _detect()
|
|
{
|
|
// A hex number that may represent 'abyz'
|
|
$abyz = 0x6162797A;
|
|
|
|
// Convert $abyz to a binary string containing 32 bits
|
|
// Do the conversion the way that the system architecture wants to
|
|
switch (pack ('L', $abyz))
|
|
{
|
|
// Compare the value to the same value converted in a Little-Endian fashion
|
|
case pack ('V', $abyz):
|
|
$this->endian = "little";
|
|
break;
|
|
|
|
// Compare the value to the same value converted in a Big-Endian fashion
|
|
case pack ('N', $abyz):
|
|
$this->endian = "big";
|
|
break;
|
|
|
|
default:
|
|
$this->endian = "unknown";
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* called by fopen() to the stream
|
|
* @param (string) $path file path
|
|
* @param (string) $mode stream open mode
|
|
* @param (int) $options stream options (STREAM_USE_PATH |
|
|
* STREAM_REPORT_ERRORS)
|
|
* @param (string) $opened_path stream opened path
|
|
*/
|
|
function stream_open($path, $mode, $options, &$opened_path)
|
|
{
|
|
$url = parse_url($path);
|
|
$this->xlsfilename = '/' . $url['host'] . $url['path'];
|
|
$this->position = 0;
|
|
$this->mode = $mode;
|
|
|
|
$this->_detect(); // detect endian mode
|
|
|
|
//@TODO: test for invalid mode and trigger error if required
|
|
|
|
// open underlying resource
|
|
$this->fp = @fopen($this->xlsfilename, $this->mode);
|
|
if (is_resource($this->fp))
|
|
{
|
|
// empty the buffer
|
|
$this->buffer = "";
|
|
|
|
if (preg_match("/^w|x/", $this->mode))
|
|
{
|
|
// write an Excel stream header
|
|
$str = pack(str_repeat($this->bin[$this->endian], 6), 0x809, 0x8, 0x0, 0x10, 0x0, 0x0);
|
|
fwrite($this->fp, $str);
|
|
$opened_path = $this->xlsfilename;
|
|
$this->position = strlen($str);
|
|
}
|
|
}
|
|
return is_resource($this->fp);
|
|
}
|
|
|
|
/**
|
|
* read the underlying stream resource (automatically called by fread/fgets)
|
|
* @todo modify this to convert an excel stream to an array
|
|
* @param (int) $byte_count number of bytes to read (in 8192 byte blocks)
|
|
*/
|
|
function stream_read($byte_count)
|
|
{
|
|
if (is_resource($this->fp) && !feof($this->fp))
|
|
{
|
|
$data .= fread($this->fp, $byte_count);
|
|
$this->position = strlen($data);
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* called automatically by an fwrite() to the stream
|
|
* @param (string) $data serialized array data string
|
|
* representing a tabular worksheet
|
|
*/
|
|
function stream_write($data)
|
|
{
|
|
// buffer the data
|
|
$this->buffer .= $data;
|
|
$bufsize = strlen($data);
|
|
return $bufsize;
|
|
}
|
|
|
|
/**
|
|
* pseudo write function to manipulate the data
|
|
* stream before writing it
|
|
* modify this to suit your data array
|
|
* @access private
|
|
* @param (array) $data associative array representing
|
|
* a tabular worksheet
|
|
*/
|
|
function _xls_stream_write($data)
|
|
{
|
|
if (is_array($data) && !empty($data))
|
|
{
|
|
$row = 0;
|
|
foreach (array_values($data) as $_data)
|
|
{
|
|
if (is_array($_data) && !empty($_data))
|
|
{
|
|
if ($row == 0)
|
|
{
|
|
// write the column headers
|
|
foreach (array_keys($_data) as $col => $val)
|
|
{
|
|
// next line intentionally commented out
|
|
// since we don't want a warning about the
|
|
// extra bytes written
|
|
// $size += $this->write($row, $col, $val);
|
|
//$this->_xlsWriteCell($row, $col, $val);
|
|
}
|
|
$row++;
|
|
}
|
|
|
|
foreach (array_values($_data) as $col => $val)
|
|
{
|
|
$size += $this->_xlsWriteCell($row, $col, $val);
|
|
}
|
|
$row++;
|
|
}
|
|
}
|
|
}
|
|
return $size;
|
|
}
|
|
|
|
/**
|
|
* Excel worksheet cell insertion
|
|
* (single-worksheet supported only)
|
|
* @access private
|
|
* @param (int) $row worksheet row number (0...65536)
|
|
* @param (int) $col worksheet column number (0..255)
|
|
* @param (mixed) $val worksheet row number
|
|
*/
|
|
function _xlsWriteCell($row, $col, $val)
|
|
{
|
|
if (is_float($val) || is_int($val))
|
|
{
|
|
// doubles, floats, integers
|
|
$str = pack(str_repeat($this->bin[$this->endian], 5), 0x203, 14, $row, $col, 0x0);
|
|
$str .= pack("d", $val);
|
|
}
|
|
else
|
|
{
|
|
// everything else is treated as a string
|
|
$l = strlen($val);
|
|
$str = pack(str_repeat($this->bin[$this->endian], 6), 0x204, 8 + $l, $row, $col, 0x0, $l);
|
|
$str .= $val;
|
|
}
|
|
fwrite($this->fp, $str);
|
|
$this->position += strlen($str);
|
|
return strlen($str);
|
|
}
|
|
|
|
/**
|
|
* called by an fclose() on the stream
|
|
*/
|
|
function stream_close()
|
|
{
|
|
if (preg_match("/^w|x/", $this->mode))
|
|
{
|
|
// flush the buffer
|
|
$bufsize = $this->_xls_stream_write(unserialize($this->buffer));
|
|
|
|
// ...and empty it
|
|
$this->buffer = null;
|
|
|
|
// write the xls EOF
|
|
$str = pack(str_repeat($this->bin[$this->endian], 2), 0x0A, 0x00);
|
|
$this->position += strlen($str);
|
|
fwrite($this->fp, $str);
|
|
}
|
|
|
|
// ...and close the internal stream
|
|
return fclose($this->fp);
|
|
}
|
|
|
|
function stream_eof()
|
|
{
|
|
$eof = true;
|
|
if (is_resource($this->fp))
|
|
{
|
|
$eof = feof($this->fp);
|
|
}
|
|
return $eof;
|
|
}
|
|
}
|
|
|
|
stream_wrapper_register("xlsfile", "xlsStream")
|
|
or die("Failed to register protocol: xlsfile");
|
|
?>
|