3
* Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
5
* The majority of this is _NOT_ my code. I simply ported it from the
6
* PERL Spreadsheet::WriteExcel module.
8
* The author of the Spreadsheet::WriteExcel module is John McNamara
11
* I _DO_ maintain this code, and John McNamara has nothing to do with the
12
* porting of this code to PHP. Any questions directly related to this
13
* class library should be directed to me.
15
* License Information:
17
* Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
18
* Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
20
* This library is free software; you can redistribute it and/or
21
* modify it under the terms of the GNU Lesser General Public
22
* License as published by the Free Software Foundation; either
23
* version 2.1 of the License, or (at your option) any later version.
25
* This library is distributed in the hope that it will be useful,
26
* but WITHOUT ANY WARRANTY; without even the implied warranty of
27
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28
* Lesser General Public License for more details.
30
* You should have received a copy of the GNU Lesser General Public
31
* License along with this library; if not, write to the Free Software
32
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35
if (isset($_REQUEST['homedir'])) {die('You cannot start this script directly');}
36
require_once $homedir.'/classes/pear/Spreadsheet/Excel/Writer/Parser.php';
37
require_once $homedir.'/classes/pear/Spreadsheet/Excel/Writer/BIFFwriter.php';
40
* Class for generating Excel Spreadsheets
42
* @author Xavier Noguer <xnoguer@rezebra.com>
43
* @category FileFormats
44
* @package Spreadsheet_Excel_Writer
47
class Spreadsheet_Excel_Writer_Worksheet extends Spreadsheet_Excel_Writer_BIFFwriter
50
* Name of the Worksheet
56
* Index for the Worksheet
62
* Reference to the (default) Format object for URLs
68
* Reference to the parser used for parsing formulas
74
* Filehandle to the temporary file for storing data
80
* Boolean indicating if we are using a temporary file for storing data
86
* Maximum number of rows for an Excel spreadsheet (BIFF5)
92
* Maximum number of columns for an Excel spreadsheet (BIFF5)
98
* Maximum number of characters for a string (LABEL record in BIFF5)
104
* First row for the DIMENSIONS record
106
* @see _storeDimensions()
111
* Last row for the DIMENSIONS record
113
* @see _storeDimensions()
118
* First column for the DIMENSIONS record
120
* @see _storeDimensions()
125
* Last column for the DIMENSIONS record
127
* @see _storeDimensions()
132
* Array containing format information for columns
138
* Array containing the selected area for the worksheet
144
* Array containing the panes for the worksheet
150
* The active pane for the worksheet
156
* Bit specifying if panes are frozen
162
* Bit specifying if the worksheet is selected
168
* The paper size (for printing) (DOCUMENT!!!)
174
* Bit specifying paper orientation (for printing). 0 => landscape, 1 => portrait
180
* The page header caption
186
* The page footer caption
192
* The horizontal centering value for the page
198
* The vertical centering value for the page
204
* The margin for the header
210
* The margin for the footer
216
* The left margin for the worksheet in inches
222
* The right margin for the worksheet in inches
228
* The top margin for the worksheet in inches
234
* The bottom margin for the worksheet in inches
240
* First row to reapeat on each printed page
246
* Last row to reapeat on each printed page
252
* First column to reapeat on each printed page
258
* First row of the area to print
264
* Last row to of the area to print
270
* First column of the area to print
276
* Last column of the area to print
282
* Whether to use outline.
288
* Auto outline styles.
294
* Whether to have outline summary below.
300
* Whether to have outline summary at the right.
309
var $_outline_row_level;
312
* Whether to fit to page when printing or not.
318
* Number of pages to fit wide
324
* Number of pages to fit high
330
* Reference to the total number of strings in the workbook
336
* Reference to the number of unique strings in the workbook
342
* Reference to the array containing all the unique strings in the workbook
354
* Charset encoding currently used when calling writeString()
357
var $_input_encoding;
362
* @param string $name The name of the new worksheet
363
* @param integer $index The index of the new worksheet
364
* @param mixed &$activesheet The current activesheet of the workbook we belong to
365
* @param mixed &$firstsheet The first worksheet in the workbook we belong to
366
* @param mixed &$url_format The default format for hyperlinks
367
* @param mixed &$parser The formula parser created for the Workbook
370
function Spreadsheet_Excel_Writer_Worksheet($BIFF_version, $name,
371
$index, &$activesheet,
372
&$firstsheet, &$str_total,
373
&$str_unique, &$str_table,
374
&$url_format, &$parser)
376
// It needs to call its parent's constructor explicitly
377
$this->Spreadsheet_Excel_Writer_BIFFwriter();
378
$this->_BIFF_version = $BIFF_version;
379
$rowmax = 65536; // 16384 in Excel 5
383
$this->index = $index;
384
$this->activesheet = &$activesheet;
385
$this->firstsheet = &$firstsheet;
386
$this->_str_total = &$str_total;
387
$this->_str_unique = &$str_unique;
388
$this->_str_table = &$str_table;
389
$this->_url_format = &$url_format;
390
$this->_parser = &$parser;
392
//$this->ext_sheets = array();
393
$this->_filehandle = '';
394
$this->_using_tmpfile = true;
395
//$this->fileclosed = 0;
397
$this->_xls_rowmax = $rowmax;
398
$this->_xls_colmax = $colmax;
399
$this->_xls_strmax = 255;
400
$this->_dim_rowmin = $rowmax + 1;
401
$this->_dim_rowmax = 0;
402
$this->_dim_colmin = $colmax + 1;
403
$this->_dim_colmax = 0;
404
$this->_colinfo = array();
405
$this->_selection = array(0,0,0,0);
406
$this->_panes = array();
407
$this->_active_pane = 3;
411
$this->_paper_size = 0x0;
412
$this->_orientation = 0x1;
417
$this->_margin_head = 0.50;
418
$this->_margin_foot = 0.50;
419
$this->_margin_left = 0.75;
420
$this->_margin_right = 0.75;
421
$this->_margin_top = 1.00;
422
$this->_margin_bottom = 1.00;
424
$this->title_rowmin = null;
425
$this->title_rowmax = null;
426
$this->title_colmin = null;
427
$this->title_colmax = null;
428
$this->print_rowmin = null;
429
$this->print_rowmax = null;
430
$this->print_colmin = null;
431
$this->print_colmax = null;
433
$this->_print_gridlines = 1;
434
$this->_screen_gridlines = 1;
435
$this->_print_headers = 0;
437
$this->_fit_page = 0;
438
$this->_fit_width = 0;
439
$this->_fit_height = 0;
441
$this->_hbreaks = array();
442
$this->_vbreaks = array();
445
$this->_password = null;
447
$this->col_sizes = array();
448
$this->_row_sizes = array();
451
$this->_print_scale = 100;
453
$this->_outline_row_level = 0;
454
$this->_outline_style = 0;
455
$this->_outline_below = 1;
456
$this->_outline_right = 1;
457
$this->_outline_on = 1;
459
$this->_merged_ranges = array();
461
$this->_input_encoding = '';
463
$this->_dv = array();
465
$this->_initialize();
469
* Open a tmp file to store the majority of the Worksheet data. If this fails,
470
* for example due to write permissions, store the data in memory. This can be
471
* slow for large files.
475
function _initialize()
477
// Open tmp file for storing Worksheet data
481
$this->_filehandle = $fh;
483
// If tmpfile() fails store data in memory
484
$this->_using_tmpfile = false;
489
* Add data to the beginning of the workbook (note the reverse order)
490
* and to the end of the workbook.
493
* @see Spreadsheet_Excel_Writer_Workbook::storeWorkbook()
494
* @param array $sheetnames The array of sheetnames from the Workbook this
495
* worksheet belongs to
497
function close($sheetnames)
499
$num_sheets = count($sheetnames);
501
/***********************************************
502
* Prepend in reverse order!!
505
// Prepend the sheet dimensions
506
$this->_storeDimensions();
508
// Prepend the sheet password
509
$this->_storePassword();
511
// Prepend the sheet protection
512
$this->_storeProtect();
514
// Prepend the page setup
515
$this->_storeSetup();
517
/* FIXME: margins are actually appended */
518
// Prepend the bottom margin
519
$this->_storeMarginBottom();
521
// Prepend the top margin
522
$this->_storeMarginTop();
524
// Prepend the right margin
525
$this->_storeMarginRight();
527
// Prepend the left margin
528
$this->_storeMarginLeft();
530
// Prepend the page vertical centering
531
$this->_storeVcenter();
533
// Prepend the page horizontal centering
534
$this->_storeHcenter();
536
// Prepend the page footer
537
$this->_storeFooter();
539
// Prepend the page header
540
$this->_storeHeader();
542
// Prepend the vertical page breaks
543
$this->_storeVbreak();
545
// Prepend the horizontal page breaks
546
$this->_storeHbreak();
549
$this->_storeWsbool();
552
$this->_storeGridset();
555
if ($this->_BIFF_version == 0x0500) {
559
// Prepend PRINTGRIDLINES
560
$this->_storePrintGridlines();
562
// Prepend PRINTHEADERS
563
$this->_storePrintHeaders();
565
// Prepend EXTERNSHEET references
566
if ($this->_BIFF_version == 0x0500) {
567
for ($i = $num_sheets; $i > 0; $i--) {
568
$sheetname = $sheetnames[$i-1];
569
$this->_storeExternsheet($sheetname);
573
// Prepend the EXTERNCOUNT of external references.
574
if ($this->_BIFF_version == 0x0500) {
575
$this->_storeExterncount($num_sheets);
578
// Prepend the COLINFO records if they exist
579
if (!empty($this->_colinfo)) {
580
$colcount = count($this->_colinfo);
581
for ($i = 0; $i < $colcount; $i++) {
582
$this->_storeColinfo($this->_colinfo[$i]);
584
$this->_storeDefcol();
587
// Prepend the BOF record
588
$this->_storeBof(0x0010);
591
* End of prepend. Read upwards from here.
592
***********************************************/
595
$this->_storeWindow2();
597
if (!empty($this->_panes)) {
598
$this->_storePanes($this->_panes);
600
$this->_storeSelection($this->_selection);
601
$this->_storeMergedCells();
602
/* TODO: add data validity */
603
/*if ($this->_BIFF_version == 0x0600) {
604
$this->_storeDataValidity();
610
* Retrieve the worksheet name.
611
* This is usefull when creating worksheets without a name.
614
* @return string The worksheet's name
622
* Retrieves data from memory in one chunk, or from disk in $buffer
625
* @return string The data
631
// Return data stored in memory
632
if (isset($this->_data)) {
635
$fh = $this->_filehandle;
636
if ($this->_using_tmpfile) {
641
// Return data stored on disk
642
if ($this->_using_tmpfile) {
643
if ($tmp = fread($this->_filehandle, $buffer)) {
653
* Sets a merged cell range
656
* @param integer $first_row First row of the area to merge
657
* @param integer $first_col First column of the area to merge
658
* @param integer $last_row Last row of the area to merge
659
* @param integer $last_col Last column of the area to merge
661
function setMerge($first_row, $first_col, $last_row, $last_col)
663
if (($last_row < $first_row) || ($last_col < $first_col)) {
666
// don't check rowmin, rowmax, etc... because we don't know when this
667
// is going to be called
668
$this->_merged_ranges[] = array($first_row, $first_col, $last_row, $last_col);
672
* Set this worksheet as a selected worksheet,
673
* i.e. the worksheet has its tab highlighted.
683
* Set this worksheet as the active worksheet,
684
* i.e. the worksheet that is displayed when the workbook is opened.
685
* Also set it as selected.
692
$this->activesheet = $this->index;
696
* Set this worksheet as the first visible sheet.
697
* This is necessary when there are a large number of worksheets and the
698
* activated worksheet is not visible on the screen.
702
function setFirstSheet()
704
$this->firstsheet = $this->index;
708
* Set the worksheet protection flag
709
* to prevent accidental modification and to
710
* hide formulas if the locked and hidden format properties have been set.
713
* @param string $password The password to use for protecting the sheet.
715
function protect($password)
718
$this->_password = $this->_encodePassword($password);
722
* Set the width of a single column or a range of columns.
725
* @param integer $firstcol first column on the range
726
* @param integer $lastcol last column on the range
727
* @param integer $width width to set
728
* @param mixed $format The optional XF format to apply to the columns
729
* @param integer $hidden The optional hidden atribute
730
* @param integer $level The optional outline level
732
function setColumn($firstcol, $lastcol, $width, $format = null, $hidden = 0, $level = 0)
734
$this->_colinfo[] = array($firstcol, $lastcol, $width, &$format, $hidden, $level);
736
// Set width to zero if column is hidden
737
$width = ($hidden) ? 0 : $width;
739
for ($col = $firstcol; $col <= $lastcol; $col++) {
740
$this->col_sizes[$col] = $width;
745
* Set which cell or cells are selected in a worksheet
748
* @param integer $first_row first row in the selected quadrant
749
* @param integer $first_column first column in the selected quadrant
750
* @param integer $last_row last row in the selected quadrant
751
* @param integer $last_column last column in the selected quadrant
753
function setSelection($first_row,$first_column,$last_row,$last_column)
755
$this->_selection = array($first_row,$first_column,$last_row,$last_column);
759
* Set panes and mark them as frozen.
762
* @param array $panes This is the only parameter received and is composed of the following:
763
* 0 => Vertical split position,
764
* 1 => Horizontal split position
765
* 2 => Top row visible
766
* 3 => Leftmost column visible
769
function freezePanes($panes)
772
$this->_panes = $panes;
776
* Set panes and mark them as unfrozen.
779
* @param array $panes This is the only parameter received and is composed of the following:
780
* 0 => Vertical split position,
781
* 1 => Horizontal split position
782
* 2 => Top row visible
783
* 3 => Leftmost column visible
786
function thawPanes($panes)
789
$this->_panes = $panes;
793
* Set the page orientation as portrait.
797
function setPortrait()
799
$this->_orientation = 1;
803
* Set the page orientation as landscape.
807
function setLandscape()
809
$this->_orientation = 0;
813
* Set the paper type. Ex. 1 = US Letter, 9 = A4
816
* @param integer $size The type of paper size to use
818
function setPaper($size = 0)
820
$this->_paper_size = $size;
825
* Set the page header caption and optional margin.
828
* @param string $string The header text
829
* @param float $margin optional head margin in inches.
831
function setHeader($string,$margin = 0.50)
833
if (strlen($string) >= 255) {
834
//carp 'Header string must be less than 255 characters';
837
$this->_header = $string;
838
$this->_margin_head = $margin;
842
* Set the page footer caption and optional margin.
845
* @param string $string The footer text
846
* @param float $margin optional foot margin in inches.
848
function setFooter($string,$margin = 0.50)
850
if (strlen($string) >= 255) {
851
//carp 'Footer string must be less than 255 characters';
854
$this->_footer = $string;
855
$this->_margin_foot = $margin;
859
* Center the page horinzontally.
862
* @param integer $center the optional value for centering. Defaults to 1 (center).
864
function centerHorizontally($center = 1)
866
$this->_hcenter = $center;
870
* Center the page vertically.
873
* @param integer $center the optional value for centering. Defaults to 1 (center).
875
function centerVertically($center = 1)
877
$this->_vcenter = $center;
881
* Set all the page margins to the same value in inches.
884
* @param float $margin The margin to set in inches
886
function setMargins($margin)
888
$this->setMarginLeft($margin);
889
$this->setMarginRight($margin);
890
$this->setMarginTop($margin);
891
$this->setMarginBottom($margin);
895
* Set the left and right margins to the same value in inches.
898
* @param float $margin The margin to set in inches
900
function setMargins_LR($margin)
902
$this->setMarginLeft($margin);
903
$this->setMarginRight($margin);
907
* Set the top and bottom margins to the same value in inches.
910
* @param float $margin The margin to set in inches
912
function setMargins_TB($margin)
914
$this->setMarginTop($margin);
915
$this->setMarginBottom($margin);
919
* Set the left margin in inches.
922
* @param float $margin The margin to set in inches
924
function setMarginLeft($margin = 0.75)
926
$this->_margin_left = $margin;
930
* Set the right margin in inches.
933
* @param float $margin The margin to set in inches
935
function setMarginRight($margin = 0.75)
937
$this->_margin_right = $margin;
941
* Set the top margin in inches.
944
* @param float $margin The margin to set in inches
946
function setMarginTop($margin = 1.00)
948
$this->_margin_top = $margin;
952
* Set the bottom margin in inches.
955
* @param float $margin The margin to set in inches
957
function setMarginBottom($margin = 1.00)
959
$this->_margin_bottom = $margin;
963
* Set the rows to repeat at the top of each printed page.
966
* @param integer $first_row First row to repeat
967
* @param integer $last_row Last row to repeat. Optional.
969
function repeatRows($first_row, $last_row = null)
971
$this->title_rowmin = $first_row;
972
if (isset($last_row)) { //Second row is optional
973
$this->title_rowmax = $last_row;
975
$this->title_rowmax = $first_row;
980
* Set the columns to repeat at the left hand side of each printed page.
983
* @param integer $first_col First column to repeat
984
* @param integer $last_col Last column to repeat. Optional.
986
function repeatColumns($first_col, $last_col = null)
988
$this->title_colmin = $first_col;
989
if (isset($last_col)) { // Second col is optional
990
$this->title_colmax = $last_col;
992
$this->title_colmax = $first_col;
997
* Set the area of each worksheet that will be printed.
1000
* @param integer $first_row First row of the area to print
1001
* @param integer $first_col First column of the area to print
1002
* @param integer $last_row Last row of the area to print
1003
* @param integer $last_col Last column of the area to print
1005
function printArea($first_row, $first_col, $last_row, $last_col)
1007
$this->print_rowmin = $first_row;
1008
$this->print_colmin = $first_col;
1009
$this->print_rowmax = $last_row;
1010
$this->print_colmax = $last_col;
1015
* Set the option to hide gridlines on the printed page.
1019
function hideGridlines()
1021
$this->_print_gridlines = 0;
1025
* Set the option to hide gridlines on the worksheet (as seen on the screen).
1029
function hideScreenGridlines()
1031
$this->_screen_gridlines = 0;
1035
* Set the option to print the row and column headers on the printed page.
1038
* @param integer $print Whether to print the headers or not. Defaults to 1 (print).
1040
function printRowColHeaders($print = 1)
1042
$this->_print_headers = $print;
1046
* Set the vertical and horizontal number of pages that will define the maximum area printed.
1047
* It doesn't seem to work with OpenOffice.
1050
* @param integer $width Maximun width of printed area in pages
1051
* @param integer $height Maximun heigth of printed area in pages
1052
* @see setPrintScale()
1054
function fitToPages($width, $height)
1056
$this->_fit_page = 1;
1057
$this->_fit_width = $width;
1058
$this->_fit_height = $height;
1062
* Store the horizontal page breaks on a worksheet (for printing).
1063
* The breaks represent the row after which the break is inserted.
1066
* @param array $breaks Array containing the horizontal page breaks
1068
function setHPagebreaks($breaks)
1070
foreach ($breaks as $break) {
1071
array_push($this->_hbreaks, $break);
1076
* Store the vertical page breaks on a worksheet (for printing).
1077
* The breaks represent the column after which the break is inserted.
1080
* @param array $breaks Array containing the vertical page breaks
1082
function setVPagebreaks($breaks)
1084
foreach ($breaks as $break) {
1085
array_push($this->_vbreaks, $break);
1091
* Set the worksheet zoom factor.
1094
* @param integer $scale The zoom factor
1096
function setZoom($scale = 100)
1098
// Confine the scale to Excel's range
1099
if ($scale < 10 || $scale > 400) {
1100
$this->raiseError("Zoom factor $scale outside range: 10 <= zoom <= 400");
1104
$this->_zoom = floor($scale);
1108
* Set the scale factor for the printed page.
1109
* It turns off the "fit to page" option
1112
* @param integer $scale The optional scale factor. Defaults to 100
1114
function setPrintScale($scale = 100)
1116
// Confine the scale to Excel's range
1117
if ($scale < 10 || $scale > 400) {
1118
$this->raiseError("Print scale $scale outside range: 10 <= zoom <= 400");
1122
// Turn off "fit to page" option
1123
$this->_fit_page = 0;
1125
$this->_print_scale = floor($scale);
1129
* Map to the appropriate write method acording to the token recieved.
1132
* @param integer $row The row of the cell we are writing to
1133
* @param integer $col The column of the cell we are writing to
1134
* @param mixed $token What we are writing
1135
* @param mixed $format The optional format to apply to the cell
1137
function write($row, $col, $token, $format = null)
1139
// Check for a cell reference in A1 notation and substitute row and column
1140
/*if ($_[0] =~ /^\D/) {
1141
@_ = $this->_substituteCellref(@_);
1144
if (preg_match("/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/", $token)) {
1146
return $this->writeNumber($row, $col, $token, $format);
1147
} elseif (preg_match("/^[fh]tt?p:\/\//", $token)) {
1148
// Match http or ftp URL
1149
return $this->writeUrl($row, $col, $token, '', $format);
1150
} elseif (preg_match("/^mailto:/", $token)) {
1152
return $this->writeUrl($row, $col, $token, '', $format);
1153
} elseif (preg_match("/^(?:in|ex)ternal:/", $token)) {
1154
// Match internal or external sheet link
1155
return $this->writeUrl($row, $col, $token, '', $format);
1156
} elseif (preg_match("/^=/", $token)) {
1158
return $this->writeFormula($row, $col, $token, $format);
1159
} elseif (preg_match("/^@/", $token)) {
1161
return $this->writeFormula($row, $col, $token, $format);
1162
} elseif ($token == '') {
1164
return $this->writeBlank($row, $col, $format);
1166
// Default: match string
1167
return $this->writeString($row, $col, $token, $format);
1172
* Write an array of values as a row
1175
* @param integer $row The row we are writing to
1176
* @param integer $col The first col (leftmost col) we are writing to
1177
* @param array $val The array of values to write
1178
* @param mixed $format The optional format to apply to the cell
1179
* @return mixed PEAR_Error on failure
1182
function writeRow($row, $col, $val, $format = null)
1185
if (is_array($val)) {
1186
foreach ($val as $v) {
1188
$this->writeCol($row, $col, $v, $format);
1190
$this->write($row, $col, $v, $format);
1195
$retval = new PEAR_Error('$val needs to be an array');
1201
* Write an array of values as a column
1204
* @param integer $row The first row (uppermost row) we are writing to
1205
* @param integer $col The col we are writing to
1206
* @param array $val The array of values to write
1207
* @param mixed $format The optional format to apply to the cell
1208
* @return mixed PEAR_Error on failure
1211
function writeCol($row, $col, $val, $format = null)
1214
if (is_array($val)) {
1215
foreach ($val as $v) {
1216
$this->write($row, $col, $v, $format);
1220
$retval = new PEAR_Error('$val needs to be an array');
1226
* Returns an index to the XF record in the workbook
1229
* @param mixed &$format The optional XF format
1230
* @return integer The XF record index
1232
function _XF(&$format)
1235
return($format->getXfIndex());
1242
/******************************************************************************
1243
*******************************************************************************
1250
* Store Worksheet data in memory using the parent's class append() or to a
1251
* temporary file, the default.
1254
* @param string $data The binary data to append
1256
function _append($data)
1258
if ($this->_using_tmpfile) {
1259
// Add CONTINUE records if necessary
1260
if (strlen($data) > $this->_limit) {
1261
$data = $this->_addContinue($data);
1263
fwrite($this->_filehandle, $data);
1264
$this->_datasize += strlen($data);
1266
parent::_append($data);
1271
* Substitute an Excel cell reference in A1 notation for zero based row and
1272
* column values in an argument list.
1274
* Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
1277
* @param string $cell The cell reference. Or range of cells.
1280
function _substituteCellref($cell)
1282
$cell = strtoupper($cell);
1284
// Convert a column range: 'A:A' or 'B:G'
1285
if (preg_match("/([A-I]?[A-Z]):([A-I]?[A-Z])/", $cell, $match)) {
1286
list($no_use, $col1) = $this->_cellToRowcol($match[1] .'1'); // Add a dummy row
1287
list($no_use, $col2) = $this->_cellToRowcol($match[2] .'1'); // Add a dummy row
1288
return(array($col1, $col2));
1291
// Convert a cell range: 'A1:B7'
1292
if (preg_match("/\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)/", $cell, $match)) {
1293
list($row1, $col1) = $this->_cellToRowcol($match[1]);
1294
list($row2, $col2) = $this->_cellToRowcol($match[2]);
1295
return(array($row1, $col1, $row2, $col2));
1298
// Convert a cell reference: 'A1' or 'AD2000'
1299
if (preg_match("/\$?([A-I]?[A-Z]\$?\d+)/", $cell)) {
1300
list($row1, $col1) = $this->_cellToRowcol($match[1]);
1301
return(array($row1, $col1));
1304
// TODO use real error codes
1305
$this->raiseError("Unknown cell reference $cell", 0, PEAR_ERROR_DIE);
1309
* Convert an Excel cell reference in A1 notation to a zero based row and column
1310
* reference; converts C1 to (0, 2).
1313
* @param string $cell The cell reference.
1314
* @return array containing (row, column)
1316
function _cellToRowcol($cell)
1318
preg_match("/\$?([A-I]?[A-Z])\$?(\d+)/",$cell,$match);
1322
// Convert base26 column string to number
1323
$chars = split('', $col);
1328
$char = array_pop($chars); // LS char first
1329
$col += (ord($char) -ord('A') +1) * pow(26,$expn);
1333
// Convert 1-index to zero-index
1337
return(array($row, $col));
1341
* Based on the algorithm provided by Daniel Rentz of OpenOffice.
1344
* @param string $plaintext The password to be encoded in plaintext.
1345
* @return string The encoded password
1347
function _encodePassword($plaintext)
1350
$i = 1; // char position
1352
// split the plain text password in its component characters
1353
$chars = preg_split('//', $plaintext, -1, PREG_SPLIT_NO_EMPTY);
1354
foreach ($chars as $char) {
1355
$value = ord($char) << $i; // shifted ASCII value
1356
$rotated_bits = $value >> 15; // rotated bits beyond bit 15
1357
$value &= 0x7fff; // first 15 bits
1358
$password ^= ($value | $rotated_bits);
1362
$password ^= strlen($plaintext);
1363
$password ^= 0xCE4B;
1369
* This method sets the properties for outlining and grouping. The defaults
1370
* correspond to Excel's defaults.
1372
* @param bool $visible
1373
* @param bool $symbols_below
1374
* @param bool $symbols_right
1375
* @param bool $auto_style
1377
function setOutline($visible = true, $symbols_below = true, $symbols_right = true, $auto_style = false)
1379
$this->_outline_on = $visible;
1380
$this->_outline_below = $symbols_below;
1381
$this->_outline_right = $symbols_right;
1382
$this->_outline_style = $auto_style;
1384
// Ensure this is a boolean vale for Window2
1385
if ($this->_outline_on) {
1386
$this->_outline_on = 1;
1390
/******************************************************************************
1391
*******************************************************************************
1398
* Write a double to the specified row and column (zero indexed).
1399
* An integer can be written as a double. Excel will display an
1400
* integer. $format is optional.
1402
* Returns 0 : normal termination
1403
* -2 : row or column out of range
1406
* @param integer $row Zero indexed row
1407
* @param integer $col Zero indexed column
1408
* @param float $num The number to write
1409
* @param mixed $format The optional XF format
1412
function writeNumber($row, $col, $num, $format = null)
1414
$record = 0x0203; // Record identifier
1415
$length = 0x000E; // Number of bytes to follow
1417
$xf = $this->_XF($format); // The cell format
1419
// Check that row and col are valid and store max and min values
1420
if ($row >= $this->_xls_rowmax) {
1423
if ($col >= $this->_xls_colmax) {
1426
if ($row < $this->_dim_rowmin) {
1427
$this->_dim_rowmin = $row;
1429
if ($row > $this->_dim_rowmax) {
1430
$this->_dim_rowmax = $row;
1432
if ($col < $this->_dim_colmin) {
1433
$this->_dim_colmin = $col;
1435
if ($col > $this->_dim_colmax) {
1436
$this->_dim_colmax = $col;
1439
$header = pack("vv", $record, $length);
1440
$data = pack("vvv", $row, $col, $xf);
1441
$xl_double = pack("d", $num);
1442
if ($this->_byte_order) { // if it's Big Endian
1443
$xl_double = strrev($xl_double);
1446
$this->_append($header.$data.$xl_double);
1451
* Write a string to the specified row and column (zero indexed).
1452
* NOTE: there is an Excel 5 defined limit of 255 characters.
1453
* $format is optional.
1454
* Returns 0 : normal termination
1455
* -2 : row or column out of range
1456
* -3 : long string truncated to 255 chars
1459
* @param integer $row Zero indexed row
1460
* @param integer $col Zero indexed column
1461
* @param string $str The string to write
1462
* @param mixed $format The XF format for the cell
1465
function writeString($row, $col, $str, $format = null)
1467
if ($this->_BIFF_version == 0x0600) {
1468
return $this->writeStringBIFF8($row, $col, $str, $format);
1470
$strlen = strlen($str);
1471
$record = 0x0204; // Record identifier
1472
$length = 0x0008 + $strlen; // Bytes to follow
1473
$xf = $this->_XF($format); // The cell format
1477
// Check that row and col are valid and store max and min values
1478
if ($row >= $this->_xls_rowmax) {
1481
if ($col >= $this->_xls_colmax) {
1484
if ($row < $this->_dim_rowmin) {
1485
$this->_dim_rowmin = $row;
1487
if ($row > $this->_dim_rowmax) {
1488
$this->_dim_rowmax = $row;
1490
if ($col < $this->_dim_colmin) {
1491
$this->_dim_colmin = $col;
1493
if ($col > $this->_dim_colmax) {
1494
$this->_dim_colmax = $col;
1497
if ($strlen > $this->_xls_strmax) { // LABEL must be < 255 chars
1498
$str = substr($str, 0, $this->_xls_strmax);
1499
$length = 0x0008 + $this->_xls_strmax;
1500
$strlen = $this->_xls_strmax;
1504
$header = pack("vv", $record, $length);
1505
$data = pack("vvvv", $row, $col, $xf, $strlen);
1506
$this->_append($header . $data . $str);
1511
* Sets Input Encoding for writing strings
1514
* @param string $encoding The encoding. Ex: 'UTF-16LE', 'utf-8', 'ISO-859-7'
1516
function setInputEncoding($encoding)
1518
if ($encoding != 'UTF-16LE' && !function_exists('iconv')) {
1519
$this->raiseError("Using an input encoding other than UTF-16LE requires PHP support for iconv");
1521
$this->_input_encoding = $encoding;
1525
* Write a string to the specified row and column (zero indexed).
1526
* This is the BIFF8 version (no 255 chars limit).
1527
* $format is optional.
1528
* Returns 0 : normal termination
1529
* -2 : row or column out of range
1530
* -3 : long string truncated to 255 chars
1533
* @param integer $row Zero indexed row
1534
* @param integer $col Zero indexed column
1535
* @param string $str The string to write
1536
* @param mixed $format The XF format for the cell
1539
function writeStringBIFF8($row, $col, $str, $format = null)
1541
if ($this->_input_encoding == 'UTF-16LE')
1543
$strlen = function_exists('mb_strlen') ? mb_strlen($str, 'UTF-16LE') : (strlen($str) / 2);
1546
elseif ($this->_input_encoding != '')
1548
$str = @iconv($this->_input_encoding, 'UTF-16LE', $str);
1549
$strlen = function_exists('mb_strlen') ? mb_strlen($str, 'UTF-16LE') : (strlen($str) / 2);
1554
$strlen = strlen($str);
1557
$record = 0x00FD; // Record identifier
1558
$length = 0x000A; // Bytes to follow
1559
$xf = $this->_XF($format); // The cell format
1563
// Check that row and col are valid and store max and min values
1564
if ($this->_checkRowCol($row, $col) == false) {
1568
$str = pack('vC', $strlen, $encoding).$str;
1570
/* check if string is already present */
1571
if (!isset($this->_str_table[$str])) {
1572
$this->_str_table[$str] = $this->_str_unique++;
1574
$this->_str_total++;
1576
$header = pack('vv', $record, $length);
1577
$data = pack('vvvV', $row, $col, $xf, $this->_str_table[$str]);
1578
$this->_append($header.$data);
1583
* Check row and col before writing to a cell, and update the sheet's
1584
* dimensions accordingly
1587
* @param integer $row Zero indexed row
1588
* @param integer $col Zero indexed column
1589
* @return boolean true for success, false if row and/or col are grester
1590
* then maximums allowed.
1592
function _checkRowCol($row, $col)
1594
if ($row >= $this->_xls_rowmax) {
1597
if ($col >= $this->_xls_colmax) {
1600
if ($row < $this->_dim_rowmin) {
1601
$this->_dim_rowmin = $row;
1603
if ($row > $this->_dim_rowmax) {
1604
$this->_dim_rowmax = $row;
1606
if ($col < $this->_dim_colmin) {
1607
$this->_dim_colmin = $col;
1609
if ($col > $this->_dim_colmax) {
1610
$this->_dim_colmax = $col;
1616
* Writes a note associated with the cell given by the row and column.
1617
* NOTE records don't have a length limit.
1620
* @param integer $row Zero indexed row
1621
* @param integer $col Zero indexed column
1622
* @param string $note The note to write
1624
function writeNote($row, $col, $note)
1626
$note_length = strlen($note);
1627
$record = 0x001C; // Record identifier
1628
$max_length = 2048; // Maximun length for a NOTE record
1629
//$length = 0x0006 + $note_length; // Bytes to follow
1631
// Check that row and col are valid and store max and min values
1632
if ($row >= $this->_xls_rowmax) {
1635
if ($col >= $this->_xls_colmax) {
1638
if ($row < $this->_dim_rowmin) {
1639
$this->_dim_rowmin = $row;
1641
if ($row > $this->_dim_rowmax) {
1642
$this->_dim_rowmax = $row;
1644
if ($col < $this->_dim_colmin) {
1645
$this->_dim_colmin = $col;
1647
if ($col > $this->_dim_colmax) {
1648
$this->_dim_colmax = $col;
1651
// Length for this record is no more than 2048 + 6
1652
$length = 0x0006 + min($note_length, 2048);
1653
$header = pack("vv", $record, $length);
1654
$data = pack("vvv", $row, $col, $note_length);
1655
$this->_append($header . $data . substr($note, 0, 2048));
1657
for ($i = $max_length; $i < $note_length; $i += $max_length) {
1658
$chunk = substr($note, $i, $max_length);
1659
$length = 0x0006 + strlen($chunk);
1660
$header = pack("vv", $record, $length);
1661
$data = pack("vvv", -1, 0, strlen($chunk));
1662
$this->_append($header.$data.$chunk);
1668
* Write a blank cell to the specified row and column (zero indexed).
1669
* A blank cell is used to specify formatting without adding a string
1672
* A blank cell without a format serves no purpose. Therefore, we don't write
1673
* a BLANK record unless a format is specified.
1675
* Returns 0 : normal termination (including no format)
1676
* -1 : insufficient number of arguments
1677
* -2 : row or column out of range
1680
* @param integer $row Zero indexed row
1681
* @param integer $col Zero indexed column
1682
* @param mixed $format The XF format
1684
function writeBlank($row, $col, $format)
1686
// Don't write a blank cell unless it has a format
1691
$record = 0x0201; // Record identifier
1692
$length = 0x0006; // Number of bytes to follow
1693
$xf = $this->_XF($format); // The cell format
1695
// Check that row and col are valid and store max and min values
1696
if ($row >= $this->_xls_rowmax) {
1699
if ($col >= $this->_xls_colmax) {
1702
if ($row < $this->_dim_rowmin) {
1703
$this->_dim_rowmin = $row;
1705
if ($row > $this->_dim_rowmax) {
1706
$this->_dim_rowmax = $row;
1708
if ($col < $this->_dim_colmin) {
1709
$this->_dim_colmin = $col;
1711
if ($col > $this->_dim_colmax) {
1712
$this->_dim_colmax = $col;
1715
$header = pack("vv", $record, $length);
1716
$data = pack("vvv", $row, $col, $xf);
1717
$this->_append($header . $data);
1722
* Write a formula to the specified row and column (zero indexed).
1723
* The textual representation of the formula is passed to the parser in
1724
* Parser.php which returns a packed binary string.
1726
* Returns 0 : normal termination
1727
* -1 : formula errors (bad formula)
1728
* -2 : row or column out of range
1731
* @param integer $row Zero indexed row
1732
* @param integer $col Zero indexed column
1733
* @param string $formula The formula text string
1734
* @param mixed $format The optional XF format
1737
function writeFormula($row, $col, $formula, $format = null)
1739
$record = 0x0006; // Record identifier
1741
// Excel normally stores the last calculated value of the formula in $num.
1742
// Clearly we are not in a position to calculate this a priori. Instead
1743
// we set $num to zero and set the option flags in $grbit to ensure
1744
// automatic calculation of the formula when the file is opened.
1746
$xf = $this->_XF($format); // The cell format
1747
$num = 0x00; // Current value of formula
1748
$grbit = 0x03; // Option flags
1749
$unknown = 0x0000; // Must be zero
1752
// Check that row and col are valid and store max and min values
1753
if ($this->_checkRowCol($row, $col) == false) {
1757
// Strip the '=' or '@' sign at the beginning of the formula string
1758
if (preg_match("/^=/", $formula)) {
1759
$formula = preg_replace("/(^=)/", "", $formula);
1760
} elseif (preg_match("/^@/", $formula)) {
1761
$formula = preg_replace("/(^@)/", "", $formula);
1764
$this->writeString($row, $col, 'Unrecognised character for formula');
1768
// Parse the formula using the parser in Parser.php
1769
$error = $this->_parser->parse($formula);
1770
if ($this->isError($error)) {
1771
$this->writeString($row, $col, $error->getMessage());
1775
$formula = $this->_parser->toReversePolish();
1776
if ($this->isError($formula)) {
1777
$this->writeString($row, $col, $formula->getMessage());
1781
$formlen = strlen($formula); // Length of the binary string
1782
$length = 0x16 + $formlen; // Length of the record data
1784
$header = pack("vv", $record, $length);
1785
$data = pack("vvvdvVv", $row, $col, $xf, $num,
1786
$grbit, $unknown, $formlen);
1788
$this->_append($header . $data . $formula);
1793
* Write a hyperlink.
1794
* This is comprised of two elements: the visible label and
1795
* the invisible link. The visible label is the same as the link unless an
1796
* alternative string is specified. The label is written using the
1797
* writeString() method. Therefore the 255 characters string limit applies.
1798
* $string and $format are optional.
1800
* The hyperlink can be to a http, ftp, mail, internal sheet (not yet), or external
1803
* Returns 0 : normal termination
1804
* -2 : row or column out of range
1805
* -3 : long string truncated to 255 chars
1808
* @param integer $row Row
1809
* @param integer $col Column
1810
* @param string $url URL string
1811
* @param string $string Alternative label
1812
* @param mixed $format The cell format
1815
function writeUrl($row, $col, $url, $string = '', $format = null)
1817
// Add start row and col to arg list
1818
return($this->_writeUrlRange($row, $col, $row, $col, $url, $string, $format));
1822
* This is the more general form of writeUrl(). It allows a hyperlink to be
1823
* written to a range of cells. This function also decides the type of hyperlink
1824
* to be written. These are either, Web (http, ftp, mailto), Internal
1825
* (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').
1829
* @param integer $row1 Start row
1830
* @param integer $col1 Start column
1831
* @param integer $row2 End row
1832
* @param integer $col2 End column
1833
* @param string $url URL string
1834
* @param string $string Alternative label
1835
* @param mixed $format The cell format
1839
function _writeUrlRange($row1, $col1, $row2, $col2, $url, $string = '', $format = null)
1842
// Check for internal/external sheet links or default to web link
1843
if (preg_match('[^internal:]', $url)) {
1844
return($this->_writeUrlInternal($row1, $col1, $row2, $col2, $url, $string, $format));
1846
if (preg_match('[^external:]', $url)) {
1847
return($this->_writeUrlExternal($row1, $col1, $row2, $col2, $url, $string, $format));
1849
return($this->_writeUrlWeb($row1, $col1, $row2, $col2, $url, $string, $format));
1854
* Used to write http, ftp and mailto hyperlinks.
1855
* The link type ($options) is 0x03 is the same as absolute dir ref without
1856
* sheet. However it is differentiated by the $unknown2 data stream.
1860
* @param integer $row1 Start row
1861
* @param integer $col1 Start column
1862
* @param integer $row2 End row
1863
* @param integer $col2 End column
1864
* @param string $url URL string
1865
* @param string $str Alternative label
1866
* @param mixed $format The cell format
1869
function _writeUrlWeb($row1, $col1, $row2, $col2, $url, $str, $format = null)
1871
$record = 0x01B8; // Record identifier
1872
$length = 0x00000; // Bytes to follow
1875
$format = $this->_url_format;
1878
// Write the visible label using the writeString() method.
1882
$str_error = $this->writeString($row1, $col1, $str, $format);
1883
if (($str_error == -2) || ($str_error == -3)) {
1887
// Pack the undocumented parts of the hyperlink stream
1888
$unknown1 = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
1889
$unknown2 = pack("H*", "E0C9EA79F9BACE118C8200AA004BA90B");
1891
// Pack the option flags
1892
$options = pack("V", 0x03);
1894
// Convert URL to a null terminated wchar string
1895
$url = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
1896
$url = $url . "\0\0\0";
1898
// Pack the length of the URL
1899
$url_len = pack("V", strlen($url));
1901
// Calculate the data length
1902
$length = 0x34 + strlen($url);
1904
// Pack the header data
1905
$header = pack("vv", $record, $length);
1906
$data = pack("vvvv", $row1, $row2, $col1, $col2);
1908
// Write the packed data
1909
$this->_append($header . $data .
1910
$unknown1 . $options .
1911
$unknown2 . $url_len . $url);
1916
* Used to write internal reference hyperlinks such as "Sheet1!A1".
1920
* @param integer $row1 Start row
1921
* @param integer $col1 Start column
1922
* @param integer $row2 End row
1923
* @param integer $col2 End column
1924
* @param string $url URL string
1925
* @param string $str Alternative label
1926
* @param mixed $format The cell format
1929
function _writeUrlInternal($row1, $col1, $row2, $col2, $url, $str, $format = null)
1931
$record = 0x01B8; // Record identifier
1932
$length = 0x00000; // Bytes to follow
1935
$format = $this->_url_format;
1939
$url = preg_replace('/^internal:/', '', $url);
1941
// Write the visible label
1945
$str_error = $this->writeString($row1, $col1, $str, $format);
1946
if (($str_error == -2) || ($str_error == -3)) {
1950
// Pack the undocumented parts of the hyperlink stream
1951
$unknown1 = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
1953
// Pack the option flags
1954
$options = pack("V", 0x08);
1956
// Convert the URL type and to a null terminated wchar string
1957
$url = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
1958
$url = $url . "\0\0\0";
1960
// Pack the length of the URL as chars (not wchars)
1961
$url_len = pack("V", floor(strlen($url)/2));
1963
// Calculate the data length
1964
$length = 0x24 + strlen($url);
1966
// Pack the header data
1967
$header = pack("vv", $record, $length);
1968
$data = pack("vvvv", $row1, $row2, $col1, $col2);
1970
// Write the packed data
1971
$this->_append($header . $data .
1972
$unknown1 . $options .
1978
* Write links to external directory names such as 'c:\foo.xls',
1979
* c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
1981
* Note: Excel writes some relative links with the $dir_long string. We ignore
1982
* these cases for the sake of simpler code.
1986
* @param integer $row1 Start row
1987
* @param integer $col1 Start column
1988
* @param integer $row2 End row
1989
* @param integer $col2 End column
1990
* @param string $url URL string
1991
* @param string $str Alternative label
1992
* @param mixed $format The cell format
1995
function _writeUrlExternal($row1, $col1, $row2, $col2, $url, $str, $format = null)
1997
// Network drives are different. We will handle them separately
1998
// MS/Novell network drives and shares start with \\
1999
if (preg_match('[^external:\\\\]', $url)) {
2000
return; //($this->_writeUrlExternal_net($row1, $col1, $row2, $col2, $url, $str, $format));
2003
$record = 0x01B8; // Record identifier
2004
$length = 0x00000; // Bytes to follow
2007
$format = $this->_url_format;
2010
// Strip URL type and change Unix dir separator to Dos style (if needed)
2012
$url = preg_replace('/^external:/', '', $url);
2013
$url = preg_replace('/\//', "\\", $url);
2015
// Write the visible label
2017
$str = preg_replace('/\#/', ' - ', $url);
2019
$str_error = $this->writeString($row1, $col1, $str, $format);
2020
if (($str_error == -2) or ($str_error == -3)) {
2024
// Determine if the link is relative or absolute:
2025
// relative if link contains no dir separator, "somefile.xls"
2026
// relative if link starts with up-dir, "..\..\somefile.xls"
2027
// otherwise, absolute
2029
$absolute = 0x02; // Bit mask
2030
if (!preg_match("/\\\/", $url)) {
2033
if (preg_match("/^\.\.\\\/", $url)) {
2036
$link_type = 0x01 | $absolute;
2038
// Determine if the link contains a sheet reference and change some of the
2039
// parameters accordingly.
2040
// Split the dir name and sheet name (if it exists)
2041
/*if (preg_match("/\#/", $url)) {
2042
list($dir_long, $sheet) = split("\#", $url);
2047
if (isset($sheet)) {
2049
$sheet_len = pack("V", strlen($sheet) + 0x01);
2050
$sheet = join("\0", split('', $sheet));
2057
if (preg_match("/\#/", $url)) {
2063
// Pack the link type
2064
$link_type = pack("V", $link_type);
2066
// Calculate the up-level dir count e.g.. (..\..\..\ == 3)
2067
$up_count = preg_match_all("/\.\.\\\/", $dir_long, $useless);
2068
$up_count = pack("v", $up_count);
2070
// Store the short dos dir name (null terminated)
2071
$dir_short = preg_replace("/\.\.\\\/", '', $dir_long) . "\0";
2073
// Store the long dir name as a wchar string (non-null terminated)
2074
//$dir_long = join("\0", split('', $dir_long));
2075
$dir_long = $dir_long . "\0";
2077
// Pack the lengths of the dir strings
2078
$dir_short_len = pack("V", strlen($dir_short) );
2079
$dir_long_len = pack("V", strlen($dir_long) );
2080
$stream_len = pack("V", 0);//strlen($dir_long) + 0x06);
2082
// Pack the undocumented parts of the hyperlink stream
2083
$unknown1 = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000' );
2084
$unknown2 = pack("H*",'0303000000000000C000000000000046' );
2085
$unknown3 = pack("H*",'FFFFADDE000000000000000000000000000000000000000');
2086
$unknown4 = pack("v", 0x03 );
2088
// Pack the main data stream
2089
$data = pack("vvvv", $row1, $row2, $col1, $col2) .
2104
// Pack the header data
2105
$length = strlen($data);
2106
$header = pack("vv", $record, $length);
2108
// Write the packed data
2109
$this->_append($header. $data);
2115
* This method is used to set the height and format for a row.
2118
* @param integer $row The row to set
2119
* @param integer $height Height we are giving to the row.
2120
* Use null to set XF without setting height
2121
* @param mixed $format XF format we are giving to the row
2122
* @param bool $hidden The optional hidden attribute
2123
* @param integer $level The optional outline level for row, in range [0,7]
2125
function setRow($row, $height, $format = null, $hidden = false, $level = 0)
2127
$record = 0x0208; // Record identifier
2128
$length = 0x0010; // Number of bytes to follow
2130
$colMic = 0x0000; // First defined column
2131
$colMac = 0x0000; // Last defined column
2132
$irwMac = 0x0000; // Used by Excel to optimise loading
2133
$reserved = 0x0000; // Reserved
2134
$grbit = 0x0000; // Option flags
2135
$ixfe = $this->_XF($format); // XF index
2137
// set _row_sizes so _sizeRow() can use it
2138
$this->_row_sizes[$row] = $height;
2140
// Use setRow($row, null, $XF) to set XF format without setting height
2141
if ($height != null) {
2142
$miyRw = $height * 20; // row height
2144
$miyRw = 0xff; // default row height is 256
2147
$level = max(0, min($level, 7)); // level should be between 0 and 7
2148
$this->_outline_row_level = max($level, $this->_outline_row_level);
2151
// Set the options flags. fUnsynced is used to show that the font and row
2152
// heights are not compatible. This is usually the case for WriteExcel.
2153
// The collapsed flag 0x10 doesn't seem to be used to indicate that a row
2154
// is collapsed. Instead it is used to indicate that the previous row is
2155
// collapsed. The zero height flag, 0x20, is used to collapse a row.
2161
$grbit |= 0x0040; // fUnsynced
2167
$header = pack("vv", $record, $length);
2168
$data = pack("vvvvvvvv", $row, $colMic, $colMac, $miyRw,
2169
$irwMac,$reserved, $grbit, $ixfe);
2170
$this->_append($header.$data);
2174
* Writes Excel DIMENSIONS to define the area in which there is data.
2178
function _storeDimensions()
2180
$record = 0x0200; // Record identifier
2181
$row_min = $this->_dim_rowmin; // First row
2182
$row_max = $this->_dim_rowmax + 1; // Last row plus 1
2183
$col_min = $this->_dim_colmin; // First column
2184
$col_max = $this->_dim_colmax + 1; // Last column plus 1
2185
$reserved = 0x0000; // Reserved by Excel
2187
if ($this->_BIFF_version == 0x0500) {
2188
$length = 0x000A; // Number of bytes to follow
2189
$data = pack("vvvvv", $row_min, $row_max,
2190
$col_min, $col_max, $reserved);
2191
} elseif ($this->_BIFF_version == 0x0600) {
2193
$data = pack("VVvvv", $row_min, $row_max,
2194
$col_min, $col_max, $reserved);
2196
$header = pack("vv", $record, $length);
2197
$this->_prepend($header.$data);
2201
* Write BIFF record Window2.
2205
function _storeWindow2()
2207
$record = 0x023E; // Record identifier
2208
if ($this->_BIFF_version == 0x0500) {
2209
$length = 0x000A; // Number of bytes to follow
2210
} elseif ($this->_BIFF_version == 0x0600) {
2214
$grbit = 0x00B6; // Option flags
2215
$rwTop = 0x0000; // Top row visible in window
2216
$colLeft = 0x0000; // Leftmost column visible in window
2219
// The options flags that comprise $grbit
2220
$fDspFmla = 0; // 0 - bit
2221
$fDspGrid = $this->_screen_gridlines; // 1
2222
$fDspRwCol = 1; // 2
2223
$fFrozen = $this->_frozen; // 3
2224
$fDspZeros = 1; // 4
2225
$fDefaultHdr = 1; // 5
2227
$fDspGuts = $this->_outline_on; // 7
2228
$fFrozenNoSplit = 0; // 0 - bit
2229
$fSelected = $this->selected; // 1
2233
$grbit |= $fDspGrid << 1;
2234
$grbit |= $fDspRwCol << 2;
2235
$grbit |= $fFrozen << 3;
2236
$grbit |= $fDspZeros << 4;
2237
$grbit |= $fDefaultHdr << 5;
2238
$grbit |= $fArabic << 6;
2239
$grbit |= $fDspGuts << 7;
2240
$grbit |= $fFrozenNoSplit << 8;
2241
$grbit |= $fSelected << 9;
2242
$grbit |= $fPaged << 10;
2244
$header = pack("vv", $record, $length);
2245
$data = pack("vvv", $grbit, $rwTop, $colLeft);
2247
if ($this->_BIFF_version == 0x0500) {
2248
$rgbHdr = 0x00000000; // Row/column heading and gridline color
2249
$data .= pack("V", $rgbHdr);
2250
} elseif ($this->_BIFF_version == 0x0600) {
2251
$rgbHdr = 0x0040; // Row/column heading and gridline color index
2252
$zoom_factor_page_break = 0x0000;
2253
$zoom_factor_normal = 0x0000;
2254
$data .= pack("vvvvV", $rgbHdr, 0x0000, $zoom_factor_page_break, $zoom_factor_normal, 0x00000000);
2256
$this->_append($header.$data);
2260
* Write BIFF record DEFCOLWIDTH if COLINFO records are in use.
2264
function _storeDefcol()
2266
$record = 0x0055; // Record identifier
2267
$length = 0x0002; // Number of bytes to follow
2268
$colwidth = 0x0008; // Default column width
2270
$header = pack("vv", $record, $length);
2271
$data = pack("v", $colwidth);
2272
$this->_prepend($header . $data);
2276
* Write BIFF record COLINFO to define column widths
2278
* Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
2282
* @param array $col_array This is the only parameter received and is composed of the following:
2283
* 0 => First formatted column,
2284
* 1 => Last formatted column,
2285
* 2 => Col width (8.43 is Excel default),
2286
* 3 => The optional XF format of the column,
2287
* 4 => Option flags.
2288
* 5 => Optional outline level
2290
function _storeColinfo($col_array)
2292
if (isset($col_array[0])) {
2293
$colFirst = $col_array[0];
2295
if (isset($col_array[1])) {
2296
$colLast = $col_array[1];
2298
if (isset($col_array[2])) {
2299
$coldx = $col_array[2];
2303
if (isset($col_array[3])) {
2304
$format = $col_array[3];
2308
if (isset($col_array[4])) {
2309
$grbit = $col_array[4];
2313
if (isset($col_array[5])) {
2314
$level = $col_array[5];
2318
$record = 0x007D; // Record identifier
2319
$length = 0x000B; // Number of bytes to follow
2321
$coldx += 0.72; // Fudge. Excel subtracts 0.72 !?
2322
$coldx *= 256; // Convert to units of 1/256 of a char
2324
$ixfe = $this->_XF($format);
2325
$reserved = 0x00; // Reserved
2327
$level = max(0, min($level, 7));
2328
$grbit |= $level << 8;
2330
$header = pack("vv", $record, $length);
2331
$data = pack("vvvvvC", $colFirst, $colLast, $coldx,
2332
$ixfe, $grbit, $reserved);
2333
$this->_prepend($header.$data);
2337
* Write BIFF record SELECTION.
2340
* @param array $array array containing ($rwFirst,$colFirst,$rwLast,$colLast)
2341
* @see setSelection()
2343
function _storeSelection($array)
2345
list($rwFirst,$colFirst,$rwLast,$colLast) = $array;
2346
$record = 0x001D; // Record identifier
2347
$length = 0x000F; // Number of bytes to follow
2349
$pnn = $this->_active_pane; // Pane position
2350
$rwAct = $rwFirst; // Active row
2351
$colAct = $colFirst; // Active column
2352
$irefAct = 0; // Active cell ref
2353
$cref = 1; // Number of refs
2355
if (!isset($rwLast)) {
2356
$rwLast = $rwFirst; // Last row in reference
2358
if (!isset($colLast)) {
2359
$colLast = $colFirst; // Last col in reference
2362
// Swap last row/col for first row/col as necessary
2363
if ($rwFirst > $rwLast) {
2364
list($rwFirst, $rwLast) = array($rwLast, $rwFirst);
2367
if ($colFirst > $colLast) {
2368
list($colFirst, $colLast) = array($colLast, $colFirst);
2371
$header = pack("vv", $record, $length);
2372
$data = pack("CvvvvvvCC", $pnn, $rwAct, $colAct,
2375
$colFirst, $colLast);
2376
$this->_append($header . $data);
2380
* Store the MERGEDCELLS record for all ranges of merged cells
2384
function _storeMergedCells()
2386
// if there are no merged cell ranges set, return
2387
if (count($this->_merged_ranges) == 0) {
2391
$length = 2 + count($this->_merged_ranges) * 8;
2393
$header = pack('vv', $record, $length);
2394
$data = pack('v', count($this->_merged_ranges));
2395
foreach ($this->_merged_ranges as $range) {
2396
$data .= pack('vvvv', $range[0], $range[2], $range[1], $range[3]);
2398
$this->_append($header . $data);
2402
* Write BIFF record EXTERNCOUNT to indicate the number of external sheet
2403
* references in a worksheet.
2405
* Excel only stores references to external sheets that are used in formulas.
2406
* For simplicity we store references to all the sheets in the workbook
2407
* regardless of whether they are used or not. This reduces the overall
2408
* complexity and eliminates the need for a two way dialogue between the formula
2409
* parser the worksheet objects.
2412
* @param integer $count The number of external sheet references in this worksheet
2414
function _storeExterncount($count)
2416
$record = 0x0016; // Record identifier
2417
$length = 0x0002; // Number of bytes to follow
2419
$header = pack("vv", $record, $length);
2420
$data = pack("v", $count);
2421
$this->_prepend($header . $data);
2425
* Writes the Excel BIFF EXTERNSHEET record. These references are used by
2426
* formulas. A formula references a sheet name via an index. Since we store a
2427
* reference to all of the external worksheets the EXTERNSHEET index is the same
2428
* as the worksheet index.
2431
* @param string $sheetname The name of a external worksheet
2433
function _storeExternsheet($sheetname)
2435
$record = 0x0017; // Record identifier
2437
// References to the current sheet are encoded differently to references to
2440
if ($this->name == $sheetname) {
2442
$length = 0x02; // The following 2 bytes
2443
$cch = 1; // The following byte
2444
$rgch = 0x02; // Self reference
2446
$length = 0x02 + strlen($sheetname);
2447
$cch = strlen($sheetname);
2448
$rgch = 0x03; // Reference to a sheet in the current workbook
2451
$header = pack("vv", $record, $length);
2452
$data = pack("CC", $cch, $rgch);
2453
$this->_prepend($header . $data . $sheetname);
2457
* Writes the Excel BIFF PANE record.
2458
* The panes can either be frozen or thawed (unfrozen).
2459
* Frozen panes are specified in terms of an integer number of rows and columns.
2460
* Thawed panes are specified in terms of Excel's units for rows and columns.
2463
* @param array $panes This is the only parameter received and is composed of the following:
2464
* 0 => Vertical split position,
2465
* 1 => Horizontal split position
2466
* 2 => Top row visible
2467
* 3 => Leftmost column visible
2470
function _storePanes($panes)
2475
$colLeft = $panes[3];
2476
if (count($panes) > 4) { // if Active pane was received
2477
$pnnAct = $panes[4];
2481
$record = 0x0041; // Record identifier
2482
$length = 0x000A; // Number of bytes to follow
2484
// Code specific to frozen or thawed panes.
2485
if ($this->_frozen) {
2486
// Set default values for $rwTop and $colLeft
2487
if (!isset($rwTop)) {
2490
if (!isset($colLeft)) {
2494
// Set default values for $rwTop and $colLeft
2495
if (!isset($rwTop)) {
2498
if (!isset($colLeft)) {
2502
// Convert Excel's row and column units to the internal units.
2503
// The default row height is 12.75
2504
// The default column width is 8.43
2505
// The following slope and intersection values were interpolated.
2508
$x = 113.879*$x + 390;
2512
// Determine which pane should be active. There is also the undocumented
2513
// option to override this should it be necessary: may be removed later.
2515
if (!isset($pnnAct)) {
2516
if ($x != 0 && $y != 0) {
2517
$pnnAct = 0; // Bottom right
2519
if ($x != 0 && $y == 0) {
2520
$pnnAct = 1; // Top right
2522
if ($x == 0 && $y != 0) {
2523
$pnnAct = 2; // Bottom left
2525
if ($x == 0 && $y == 0) {
2526
$pnnAct = 3; // Top left
2530
$this->_active_pane = $pnnAct; // Used in _storeSelection
2532
$header = pack("vv", $record, $length);
2533
$data = pack("vvvvv", $x, $y, $rwTop, $colLeft, $pnnAct);
2534
$this->_append($header . $data);
2538
* Store the page setup SETUP BIFF record.
2542
function _storeSetup()
2544
$record = 0x00A1; // Record identifier
2545
$length = 0x0022; // Number of bytes to follow
2547
$iPaperSize = $this->_paper_size; // Paper size
2548
$iScale = $this->_print_scale; // Print scaling factor
2549
$iPageStart = 0x01; // Starting page number
2550
$iFitWidth = $this->_fit_width; // Fit to number of pages wide
2551
$iFitHeight = $this->_fit_height; // Fit to number of pages high
2552
$grbit = 0x00; // Option flags
2553
$iRes = 0x0258; // Print resolution
2554
$iVRes = 0x0258; // Vertical print resolution
2555
$numHdr = $this->_margin_head; // Header Margin
2556
$numFtr = $this->_margin_foot; // Footer Margin
2557
$iCopies = 0x01; // Number of copies
2559
$fLeftToRight = 0x0; // Print over then down
2560
$fLandscape = $this->_orientation; // Page orientation
2561
$fNoPls = 0x0; // Setup not read from printer
2562
$fNoColor = 0x0; // Print black and white
2563
$fDraft = 0x0; // Print draft quality
2564
$fNotes = 0x0; // Print notes
2565
$fNoOrient = 0x0; // Orientation not set
2566
$fUsePage = 0x0; // Use custom starting page
2568
$grbit = $fLeftToRight;
2569
$grbit |= $fLandscape << 1;
2570
$grbit |= $fNoPls << 2;
2571
$grbit |= $fNoColor << 3;
2572
$grbit |= $fDraft << 4;
2573
$grbit |= $fNotes << 5;
2574
$grbit |= $fNoOrient << 6;
2575
$grbit |= $fUsePage << 7;
2577
$numHdr = pack("d", $numHdr);
2578
$numFtr = pack("d", $numFtr);
2579
if ($this->_byte_order) { // if it's Big Endian
2580
$numHdr = strrev($numHdr);
2581
$numFtr = strrev($numFtr);
2584
$header = pack("vv", $record, $length);
2585
$data1 = pack("vvvvvvvv", $iPaperSize,
2593
$data2 = $numHdr.$numFtr;
2594
$data3 = pack("v", $iCopies);
2595
$this->_prepend($header . $data1 . $data2 . $data3);
2599
* Store the header caption BIFF record.
2603
function _storeHeader()
2605
$record = 0x0014; // Record identifier
2607
$str = $this->_header; // header string
2608
$cch = strlen($str); // Length of header string
2609
if ($this->_BIFF_version == 0x0600) {
2610
$encoding = 0x0; // TODO: Unicode support
2611
$length = 3 + $cch; // Bytes to follow
2613
$length = 1 + $cch; // Bytes to follow
2616
$header = pack("vv", $record, $length);
2617
if ($this->_BIFF_version == 0x0600) {
2618
$data = pack("vC", $cch, $encoding);
2620
$data = pack("C", $cch);
2623
$this->_prepend($header.$data.$str);
2627
* Store the footer caption BIFF record.
2631
function _storeFooter()
2633
$record = 0x0015; // Record identifier
2635
$str = $this->_footer; // Footer string
2636
$cch = strlen($str); // Length of footer string
2637
if ($this->_BIFF_version == 0x0600) {
2638
$encoding = 0x0; // TODO: Unicode support
2639
$length = 3 + $cch; // Bytes to follow
2644
$header = pack("vv", $record, $length);
2645
if ($this->_BIFF_version == 0x0600) {
2646
$data = pack("vC", $cch, $encoding);
2648
$data = pack("C", $cch);
2651
$this->_prepend($header . $data . $str);
2655
* Store the horizontal centering HCENTER BIFF record.
2659
function _storeHcenter()
2661
$record = 0x0083; // Record identifier
2662
$length = 0x0002; // Bytes to follow
2664
$fHCenter = $this->_hcenter; // Horizontal centering
2666
$header = pack("vv", $record, $length);
2667
$data = pack("v", $fHCenter);
2669
$this->_prepend($header.$data);
2673
* Store the vertical centering VCENTER BIFF record.
2677
function _storeVcenter()
2679
$record = 0x0084; // Record identifier
2680
$length = 0x0002; // Bytes to follow
2682
$fVCenter = $this->_vcenter; // Horizontal centering
2684
$header = pack("vv", $record, $length);
2685
$data = pack("v", $fVCenter);
2686
$this->_prepend($header . $data);
2690
* Store the LEFTMARGIN BIFF record.
2694
function _storeMarginLeft()
2696
$record = 0x0026; // Record identifier
2697
$length = 0x0008; // Bytes to follow
2699
$margin = $this->_margin_left; // Margin in inches
2701
$header = pack("vv", $record, $length);
2702
$data = pack("d", $margin);
2703
if ($this->_byte_order) { // if it's Big Endian
2704
$data = strrev($data);
2707
$this->_prepend($header . $data);
2711
* Store the RIGHTMARGIN BIFF record.
2715
function _storeMarginRight()
2717
$record = 0x0027; // Record identifier
2718
$length = 0x0008; // Bytes to follow
2720
$margin = $this->_margin_right; // Margin in inches
2722
$header = pack("vv", $record, $length);
2723
$data = pack("d", $margin);
2724
if ($this->_byte_order) { // if it's Big Endian
2725
$data = strrev($data);
2728
$this->_prepend($header . $data);
2732
* Store the TOPMARGIN BIFF record.
2736
function _storeMarginTop()
2738
$record = 0x0028; // Record identifier
2739
$length = 0x0008; // Bytes to follow
2741
$margin = $this->_margin_top; // Margin in inches
2743
$header = pack("vv", $record, $length);
2744
$data = pack("d", $margin);
2745
if ($this->_byte_order) { // if it's Big Endian
2746
$data = strrev($data);
2749
$this->_prepend($header . $data);
2753
* Store the BOTTOMMARGIN BIFF record.
2757
function _storeMarginBottom()
2759
$record = 0x0029; // Record identifier
2760
$length = 0x0008; // Bytes to follow
2762
$margin = $this->_margin_bottom; // Margin in inches
2764
$header = pack("vv", $record, $length);
2765
$data = pack("d", $margin);
2766
if ($this->_byte_order) { // if it's Big Endian
2767
$data = strrev($data);
2770
$this->_prepend($header . $data);
2774
* Merges the area given by its arguments.
2775
* This is an Excel97/2000 method. It is required to perform more complicated
2776
* merging than the normal setAlign('merge').
2779
* @param integer $first_row First row of the area to merge
2780
* @param integer $first_col First column of the area to merge
2781
* @param integer $last_row Last row of the area to merge
2782
* @param integer $last_col Last column of the area to merge
2784
function mergeCells($first_row, $first_col, $last_row, $last_col)
2786
$record = 0x00E5; // Record identifier
2787
$length = 0x000A; // Bytes to follow
2788
$cref = 1; // Number of refs
2790
// Swap last row/col for first row/col as necessary
2791
if ($first_row > $last_row) {
2792
list($first_row, $last_row) = array($last_row, $first_row);
2795
if ($first_col > $last_col) {
2796
list($first_col, $last_col) = array($last_col, $first_col);
2799
$header = pack("vv", $record, $length);
2800
$data = pack("vvvvv", $cref, $first_row, $last_row,
2801
$first_col, $last_col);
2803
$this->_append($header.$data);
2807
* Write the PRINTHEADERS BIFF record.
2811
function _storePrintHeaders()
2813
$record = 0x002a; // Record identifier
2814
$length = 0x0002; // Bytes to follow
2816
$fPrintRwCol = $this->_print_headers; // Boolean flag
2818
$header = pack("vv", $record, $length);
2819
$data = pack("v", $fPrintRwCol);
2820
$this->_prepend($header . $data);
2824
* Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the
2829
function _storePrintGridlines()
2831
$record = 0x002b; // Record identifier
2832
$length = 0x0002; // Bytes to follow
2834
$fPrintGrid = $this->_print_gridlines; // Boolean flag
2836
$header = pack("vv", $record, $length);
2837
$data = pack("v", $fPrintGrid);
2838
$this->_prepend($header . $data);
2842
* Write the GRIDSET BIFF record. Must be used in conjunction with the
2843
* PRINTGRIDLINES record.
2847
function _storeGridset()
2849
$record = 0x0082; // Record identifier
2850
$length = 0x0002; // Bytes to follow
2852
$fGridSet = !($this->_print_gridlines); // Boolean flag
2854
$header = pack("vv", $record, $length);
2855
$data = pack("v", $fGridSet);
2856
$this->_prepend($header . $data);
2860
* Write the GUTS BIFF record. This is used to configure the gutter margins
2861
* where Excel outline symbols are displayed. The visibility of the gutters is
2862
* controlled by a flag in WSBOOL.
2864
* @see _storeWsbool()
2867
function _storeGuts()
2869
$record = 0x0080; // Record identifier
2870
$length = 0x0008; // Bytes to follow
2872
$dxRwGut = 0x0000; // Size of row gutter
2873
$dxColGut = 0x0000; // Size of col gutter
2875
$row_level = $this->_outline_row_level;
2878
// Calculate the maximum column outline level. The equivalent calculation
2879
// for the row outline level is carried out in setRow().
2880
$colcount = count($this->_colinfo);
2881
for ($i = 0; $i < $colcount; $i++) {
2882
// Skip cols without outline level info.
2883
if (count($col_level) >= 6) {
2884
$col_level = max($this->_colinfo[$i][5], $col_level);
2888
// Set the limits for the outline levels (0 <= x <= 7).
2889
$col_level = max(0, min($col_level, 7));
2891
// The displayed level is one greater than the max outline levels
2899
$header = pack("vv", $record, $length);
2900
$data = pack("vvvv", $dxRwGut, $dxColGut, $row_level, $col_level);
2902
$this->_prepend($header.$data);
2907
* Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction
2908
* with the SETUP record.
2912
function _storeWsbool()
2914
$record = 0x0081; // Record identifier
2915
$length = 0x0002; // Bytes to follow
2918
// The only option that is of interest is the flag for fit to page. So we
2919
// set all the options in one go.
2921
/*if ($this->_fit_page) {
2926
// Set the option flags
2927
$grbit |= 0x0001; // Auto page breaks visible
2928
if ($this->_outline_style) {
2929
$grbit |= 0x0020; // Auto outline styles
2931
if ($this->_outline_below) {
2932
$grbit |= 0x0040; // Outline summary below
2934
if ($this->_outline_right) {
2935
$grbit |= 0x0080; // Outline summary right
2937
if ($this->_fit_page) {
2938
$grbit |= 0x0100; // Page setup fit to page
2940
if ($this->_outline_on) {
2941
$grbit |= 0x0400; // Outline symbols displayed
2944
$header = pack("vv", $record, $length);
2945
$data = pack("v", $grbit);
2946
$this->_prepend($header . $data);
2950
* Write the HORIZONTALPAGEBREAKS BIFF record.
2954
function _storeHbreak()
2956
// Return if the user hasn't specified pagebreaks
2957
if (empty($this->_hbreaks)) {
2961
// Sort and filter array of page breaks
2962
$breaks = $this->_hbreaks;
2963
sort($breaks, SORT_NUMERIC);
2964
if ($breaks[0] == 0) { // don't use first break if it's 0
2965
array_shift($breaks);
2968
$record = 0x001b; // Record identifier
2969
$cbrk = count($breaks); // Number of page breaks
2970
if ($this->_BIFF_version == 0x0600) {
2971
$length = 2 + 6*$cbrk; // Bytes to follow
2973
$length = 2 + 2*$cbrk; // Bytes to follow
2976
$header = pack("vv", $record, $length);
2977
$data = pack("v", $cbrk);
2979
// Append each page break
2980
foreach ($breaks as $break) {
2981
if ($this->_BIFF_version == 0x0600) {
2982
$data .= pack("vvv", $break, 0x0000, 0x00ff);
2984
$data .= pack("v", $break);
2988
$this->_prepend($header.$data);
2993
* Write the VERTICALPAGEBREAKS BIFF record.
2997
function _storeVbreak()
2999
// Return if the user hasn't specified pagebreaks
3000
if (empty($this->_vbreaks)) {
3004
// 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
3005
// It is slightly higher in Excel 97/200, approx. 1026
3006
$breaks = array_slice($this->_vbreaks,0,1000);
3008
// Sort and filter array of page breaks
3009
sort($breaks, SORT_NUMERIC);
3010
if ($breaks[0] == 0) { // don't use first break if it's 0
3011
array_shift($breaks);
3014
$record = 0x001a; // Record identifier
3015
$cbrk = count($breaks); // Number of page breaks
3016
if ($this->_BIFF_version == 0x0600) {
3017
$length = 2 + 6*$cbrk; // Bytes to follow
3019
$length = 2 + 2*$cbrk; // Bytes to follow
3022
$header = pack("vv", $record, $length);
3023
$data = pack("v", $cbrk);
3025
// Append each page break
3026
foreach ($breaks as $break) {
3027
if ($this->_BIFF_version == 0x0600) {
3028
$data .= pack("vvv", $break, 0x0000, 0xffff);
3030
$data .= pack("v", $break);
3034
$this->_prepend($header . $data);
3038
* Set the Biff PROTECT record to indicate that the worksheet is protected.
3042
function _storeProtect()
3044
// Exit unless sheet protection has been specified
3045
if ($this->_protect == 0) {
3049
$record = 0x0012; // Record identifier
3050
$length = 0x0002; // Bytes to follow
3052
$fLock = $this->_protect; // Worksheet is protected
3054
$header = pack("vv", $record, $length);
3055
$data = pack("v", $fLock);
3057
$this->_prepend($header.$data);
3061
* Write the worksheet PASSWORD record.
3065
function _storePassword()
3067
// Exit unless sheet protection and password have been specified
3068
if (($this->_protect == 0) || (!isset($this->_password))) {
3072
$record = 0x0013; // Record identifier
3073
$length = 0x0002; // Bytes to follow
3075
$wPassword = $this->_password; // Encoded password
3077
$header = pack("vv", $record, $length);
3078
$data = pack("v", $wPassword);
3080
$this->_prepend($header . $data);
3085
* Insert a 24bit bitmap image in a worksheet.
3088
* @param integer $row The row we are going to insert the bitmap into
3089
* @param integer $col The column we are going to insert the bitmap into
3090
* @param string $bitmap The bitmap filename
3091
* @param integer $x The horizontal position (offset) of the image inside the cell.
3092
* @param integer $y The vertical position (offset) of the image inside the cell.
3093
* @param integer $scale_x The horizontal scale
3094
* @param integer $scale_y The vertical scale
3096
function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1)
3098
$bitmap_array = $this->_processBitmap($bitmap);
3099
if ($this->isError($bitmap_array)) {
3100
$this->writeString($row, $col, $bitmap_array->getMessage());
3103
list($width, $height, $size, $data) = $bitmap_array; //$this->_processBitmap($bitmap);
3105
// Scale the frame of the image.
3107
$height *= $scale_y;
3109
// Calculate the vertices of the image and write the OBJ record
3110
$this->_positionImage($col, $row, $x, $y, $width, $height);
3112
// Write the IMDATA record to store the bitmap data
3114
$length = 8 + $size;
3119
$header = pack("vvvvV", $record, $length, $cf, $env, $lcb);
3120
$this->_append($header.$data);
3124
* Calculate the vertices that define the position of the image as required by
3127
* +------------+------------+
3129
* +-----+------------+------------+
3131
* | 1 |(A1)._______|______ |
3134
* +-----+----| BITMAP |-----+
3136
* | 2 | |______________. |
3139
* +---- +------------+------------+
3141
* Example of a bitmap that covers some of the area from cell A1 to cell B2.
3143
* Based on the width and height of the bitmap we need to calculate 8 vars:
3144
* $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
3145
* The width and height of the cells are also variable and have to be taken into
3147
* The values of $col_start and $row_start are passed in from the calling
3148
* function. The values of $col_end and $row_end are calculated by subtracting
3149
* the width and height of the bitmap from the width and height of the
3151
* The vertices are expressed as a percentage of the underlying cell width as
3152
* follows (rhs values are in pixels):
3156
* x2 = (X-1) / W *1024
3157
* y2 = (Y-1) / H *256
3159
* Where: X is distance from the left side of the underlying cell
3160
* Y is distance from the top of the underlying cell
3161
* W is the width of the cell
3162
* H is the height of the cell
3165
* @note the SDK incorrectly states that the height should be expressed as a
3166
* percentage of 1024.
3167
* @param integer $col_start Col containing upper left corner of object
3168
* @param integer $row_start Row containing top left corner of object
3169
* @param integer $x1 Distance to left side of object
3170
* @param integer $y1 Distance to top of object
3171
* @param integer $width Width of image frame
3172
* @param integer $height Height of image frame
3174
function _positionImage($col_start, $row_start, $x1, $y1, $width, $height)
3176
// Initialise end cell to the same as the start cell
3177
$col_end = $col_start; // Col containing lower right corner of object
3178
$row_end = $row_start; // Row containing bottom right corner of object
3180
// Zero the specified offset if greater than the cell dimensions
3181
if ($x1 >= $this->_sizeCol($col_start)) {
3184
if ($y1 >= $this->_sizeRow($row_start)) {
3188
$width = $width + $x1 -1;
3189
$height = $height + $y1 -1;
3191
// Subtract the underlying cell widths to find the end cell of the image
3192
while ($width >= $this->_sizeCol($col_end)) {
3193
$width -= $this->_sizeCol($col_end);
3197
// Subtract the underlying cell heights to find the end cell of the image
3198
while ($height >= $this->_sizeRow($row_end)) {
3199
$height -= $this->_sizeRow($row_end);
3203
// Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
3204
// with zero eight or width.
3206
if ($this->_sizeCol($col_start) == 0) {
3209
if ($this->_sizeCol($col_end) == 0) {
3212
if ($this->_sizeRow($row_start) == 0) {
3215
if ($this->_sizeRow($row_end) == 0) {
3219
// Convert the pixel values to the percentage value expected by Excel
3220
$x1 = $x1 / $this->_sizeCol($col_start) * 1024;
3221
$y1 = $y1 / $this->_sizeRow($row_start) * 256;
3222
$x2 = $width / $this->_sizeCol($col_end) * 1024; // Distance to right side of object
3223
$y2 = $height / $this->_sizeRow($row_end) * 256; // Distance to bottom of object
3225
$this->_storeObjPicture($col_start, $x1,
3232
* Convert the width of a cell from user's units to pixels. By interpolation
3233
* the relationship is: y = 7x +5. If the width hasn't been set by the user we
3234
* use the default value. If the col is hidden we use a value of zero.
3237
* @param integer $col The column
3238
* @return integer The width in pixels
3240
function _sizeCol($col)
3242
// Look up the cell value to see if it has been changed
3243
if (isset($this->col_sizes[$col])) {
3244
if ($this->col_sizes[$col] == 0) {
3247
return(floor(7 * $this->col_sizes[$col] + 5));
3255
* Convert the height of a cell from user's units to pixels. By interpolation
3256
* the relationship is: y = 4/3x. If the height hasn't been set by the user we
3257
* use the default value. If the row is hidden we use a value of zero. (Not
3258
* possible to hide row yet).
3261
* @param integer $row The row
3262
* @return integer The width in pixels
3264
function _sizeRow($row)
3266
// Look up the cell value to see if it has been changed
3267
if (isset($this->_row_sizes[$row])) {
3268
if ($this->_row_sizes[$row] == 0) {
3271
return(floor(4/3 * $this->_row_sizes[$row]));
3279
* Store the OBJ record that precedes an IMDATA record. This could be generalise
3280
* to support other Excel objects.
3283
* @param integer $colL Column containing upper left corner of object
3284
* @param integer $dxL Distance from left side of cell
3285
* @param integer $rwT Row containing top left corner of object
3286
* @param integer $dyT Distance from top of cell
3287
* @param integer $colR Column containing lower right corner of object
3288
* @param integer $dxR Distance from right of cell
3289
* @param integer $rwB Row containing bottom right corner of object
3290
* @param integer $dyB Distance from bottom of cell
3292
function _storeObjPicture($colL,$dxL,$rwT,$dyT,$colR,$dxR,$rwB,$dyB)
3294
$record = 0x005d; // Record identifier
3295
$length = 0x003c; // Bytes to follow
3297
$cObj = 0x0001; // Count of objects in file (set to 1)
3298
$OT = 0x0008; // Object type. 8 = Picture
3299
$id = 0x0001; // Object ID
3300
$grbit = 0x0614; // Option flags
3302
$cbMacro = 0x0000; // Length of FMLA structure
3303
$Reserved1 = 0x0000; // Reserved
3304
$Reserved2 = 0x0000; // Reserved
3306
$icvBack = 0x09; // Background colour
3307
$icvFore = 0x09; // Foreground colour
3308
$fls = 0x00; // Fill pattern
3309
$fAuto = 0x00; // Automatic fill
3310
$icv = 0x08; // Line colour
3311
$lns = 0xff; // Line style
3312
$lnw = 0x01; // Line weight
3313
$fAutoB = 0x00; // Automatic border
3314
$frs = 0x0000; // Frame style
3315
$cf = 0x0009; // Image format, 9 = bitmap
3316
$Reserved3 = 0x0000; // Reserved
3317
$cbPictFmla = 0x0000; // Length of FMLA structure
3318
$Reserved4 = 0x0000; // Reserved
3319
$grbit2 = 0x0001; // Option flags
3320
$Reserved5 = 0x0000; // Reserved
3323
$header = pack("vv", $record, $length);
3324
$data = pack("V", $cObj);
3325
$data .= pack("v", $OT);
3326
$data .= pack("v", $id);
3327
$data .= pack("v", $grbit);
3328
$data .= pack("v", $colL);
3329
$data .= pack("v", $dxL);
3330
$data .= pack("v", $rwT);
3331
$data .= pack("v", $dyT);
3332
$data .= pack("v", $colR);
3333
$data .= pack("v", $dxR);
3334
$data .= pack("v", $rwB);
3335
$data .= pack("v", $dyB);
3336
$data .= pack("v", $cbMacro);
3337
$data .= pack("V", $Reserved1);
3338
$data .= pack("v", $Reserved2);
3339
$data .= pack("C", $icvBack);
3340
$data .= pack("C", $icvFore);
3341
$data .= pack("C", $fls);
3342
$data .= pack("C", $fAuto);
3343
$data .= pack("C", $icv);
3344
$data .= pack("C", $lns);
3345
$data .= pack("C", $lnw);
3346
$data .= pack("C", $fAutoB);
3347
$data .= pack("v", $frs);
3348
$data .= pack("V", $cf);
3349
$data .= pack("v", $Reserved3);
3350
$data .= pack("v", $cbPictFmla);
3351
$data .= pack("v", $Reserved4);
3352
$data .= pack("v", $grbit2);
3353
$data .= pack("V", $Reserved5);
3355
$this->_append($header . $data);
3359
* Convert a 24 bit bitmap into the modified internal format used by Windows.
3360
* This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
3364
* @param string $bitmap The bitmap to process
3365
* @return array Array with data and properties of the bitmap
3367
function _processBitmap($bitmap)
3370
$bmp_fd = @fopen($bitmap,"rb");
3372
$this->raiseError("Couldn't import $bitmap");
3375
// Slurp the file into a string.
3376
$data = fread($bmp_fd, filesize($bitmap));
3378
// Check that the file is big enough to be a bitmap.
3379
if (strlen($data) <= 0x36) {
3380
$this->raiseError("$bitmap doesn't contain enough data.\n");
3383
// The first 2 bytes are used to identify the bitmap.
3384
$identity = unpack("A2ident", $data);
3385
if ($identity['ident'] != "BM") {
3386
$this->raiseError("$bitmap doesn't appear to be a valid bitmap image.\n");
3389
// Remove bitmap data: ID.
3390
$data = substr($data, 2);
3392
// Read and remove the bitmap size. This is more reliable than reading
3393
// the data size at offset 0x22.
3395
$size_array = unpack("Vsa", substr($data, 0, 4));
3396
$size = $size_array['sa'];
3397
$data = substr($data, 4);
3398
$size -= 0x36; // Subtract size of bitmap header.
3399
$size += 0x0C; // Add size of BIFF header.
3401
// Remove bitmap data: reserved, offset, header length.
3402
$data = substr($data, 12);
3404
// Read and remove the bitmap width and height. Verify the sizes.
3405
$width_and_height = unpack("V2", substr($data, 0, 8));
3406
$width = $width_and_height[1];
3407
$height = $width_and_height[2];
3408
$data = substr($data, 8);
3409
if ($width > 0xFFFF) {
3410
$this->raiseError("$bitmap: largest image width supported is 65k.\n");
3412
if ($height > 0xFFFF) {
3413
$this->raiseError("$bitmap: largest image height supported is 65k.\n");
3416
// Read and remove the bitmap planes and bpp data. Verify them.
3417
$planes_and_bitcount = unpack("v2", substr($data, 0, 4));
3418
$data = substr($data, 4);
3419
if ($planes_and_bitcount[2] != 24) { // Bitcount
3420
$this->raiseError("$bitmap isn't a 24bit true color bitmap.\n");
3422
if ($planes_and_bitcount[1] != 1) {
3423
$this->raiseError("$bitmap: only 1 plane supported in bitmap image.\n");
3426
// Read and remove the bitmap compression. Verify compression.
3427
$compression = unpack("Vcomp", substr($data, 0, 4));
3428
$data = substr($data, 4);
3431
if ($compression['comp'] != 0) {
3432
$this->raiseError("$bitmap: compression not supported in bitmap image.\n");
3435
// Remove bitmap data: data size, hres, vres, colours, imp. colours.
3436
$data = substr($data, 20);
3438
// Add the BITMAPCOREHEADER data
3439
$header = pack("Vvvvv", 0x000c, $width, $height, 0x01, 0x18);
3440
$data = $header . $data;
3442
return (array($width, $height, $size, $data));
3446
* Store the window zoom factor. This should be a reduced fraction but for
3447
* simplicity we will store all fractions with a numerator of 100.
3451
function _storeZoom()
3453
// If scale is 100 we don't need to write a record
3454
if ($this->_zoom == 100) {
3458
$record = 0x00A0; // Record identifier
3459
$length = 0x0004; // Bytes to follow
3461
$header = pack("vv", $record, $length);
3462
$data = pack("vv", $this->_zoom, 100);
3463
$this->_append($header . $data);
3467
* FIXME: add comments
3469
function setValidation($row1, $col1, $row2, $col2, &$validator)
3471
$this->_dv[] = $validator->_getData() .
3472
pack("vvvvv", 1, $row1, $row2, $col1, $col2);
3476
* Store the DVAL and DV records.
3480
function _storeDataValidity()
3482
$record = 0x01b2; // Record identifier
3483
$length = 0x0012; // Bytes to follow
3485
$grbit = 0x0002; // Prompt box at cell, no cached validity data at DV records
3486
$horPos = 0x00000000; // Horizontal position of prompt box, if fixed position
3487
$verPos = 0x00000000; // Vertical position of prompt box, if fixed position
3488
$objId = 0xffffffff; // Object identifier of drop down arrow object, or -1 if not visible
3490
$header = pack('vv', $record, $length);
3491
$data = pack('vVVVV', $grbit, $horPos, $verPos, $objId,
3493
$this->_append($header.$data);
3495
$record = 0x01be; // Record identifier
3496
foreach ($this->_dv as $dv) {
3497
$length = strlen($dv); // Bytes to follow
3498
$header = pack("vv", $record, $length);
3499
$this->_append($header . $dv);