Code

Fixed printf , parameter was missing,
[gosa.git] / include / php_writeexcel / class.writeexcel_worksheet.inc.php
1 <?php
3 /*
4  * Copyleft 2002 Johann Hanne
5  *
6  * This is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This software is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this software; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place,
19  * Suite 330, Boston, MA  02111-1307 USA
20  */
22 /*
23  * This is the Spreadsheet::WriteExcel Perl package ported to PHP
24  * Spreadsheet::WriteExcel was written by John McNamara, jmcnamara@cpan.org
25  */
27 require_once "class.writeexcel_biffwriter.inc.php";
29 class writeexcel_worksheet extends writeexcel_biffwriter {
31     var $_name;
32     var $_index;
33     var $_activesheet;
34     var $_firstsheet;
35     var $_url_format;
36     var $_parser;
37     var $_tempdir;
39     var $_ext_sheets;
40     var $_using_tmpfile;
41     var $_filehandle;
42     var $_fileclosed;
43     var $_offset;
44     var $_xls_rowmax;
45     var $_xls_colmax;
46     var $_xls_strmax;
47     var $_dim_rowmin;
48     var $_dim_rowmax;
49     var $_dim_colmin;
50     var $_dim_colmax;
51     var $_colinfo;
52     var $_selection;
53     var $_panes;
54     var $_active_pane;
55     var $_frozen;
56     var $_selected;
58     var $_paper_size;
59     var $_orientation;
60     var $_header;
61     var $_footer;
62     var $_hcenter;
63     var $_vcenter;
64     var $_margin_head;
65     var $_margin_foot;
66     var $_margin_left;
67     var $_margin_right;
68     var $_margin_top;
69     var $_margin_bottom;
71     var $_title_rowmin;
72     var $_title_rowmax;
73     var $_title_colmin;
74     var $_title_colmax;
75     var $_print_rowmin;
76     var $_print_rowmax;
77     var $_print_colmin;
78     var $_print_colmax;
80     var $_print_gridlines;
81     var $_screen_gridlines;
82     var $_print_headers;
84     var $_fit_page;
85     var $_fit_width;
86     var $_fit_height;
88     var $_hbreaks;
89     var $_vbreaks;
91     var $_protect;
92     var $_password;
94     var $_col_sizes;
95     var $_row_sizes;
97     var $_col_formats;
98     var $_row_formats;
100     var $_zoom;
101     var $_print_scale;
103     /*
104      * Constructor. Creates a new Worksheet object from a BIFFwriter object
105      */
106     function writeexcel_worksheet($name, $index, &$activesheet, &$firstsheet,
107                                   &$url_format, &$parser, $tempdir) {
109         $this->writeexcel_biffwriter();
111         $rowmax                   = 65536; // 16384 in Excel 5
112         $colmax                   = 256;
113         $strmax                   = 255;
115         $this->_name              = $name;
116         $this->_index             = $index;
117         $this->_activesheet       = &$activesheet;
118         $this->_firstsheet        = &$firstsheet;
119         $this->_url_format        = &$url_format;
120         $this->_parser            = &$parser;
121         $this->_tempdir           = $tempdir;
123         $this->_ext_sheets        = array();
124         $this->_using_tmpfile     = 1;
125         $this->_filehandle        = false;
126         $this->_fileclosed        = 0;
127         $this->_offset            = 0;
128         $this->_xls_rowmax        = $rowmax;
129         $this->_xls_colmax        = $colmax;
130         $this->_xls_strmax        = $strmax;
131         $this->_dim_rowmin        = $rowmax +1;
132         $this->_dim_rowmax        = 0;
133         $this->_dim_colmin        = $colmax +1;
134         $this->_dim_colmax        = 0;
135         $this->_colinfo           = array();
136         $this->_selection         = array(0, 0);
137         $this->_panes             = array();
138         $this->_active_pane       = 3;
139         $this->_frozen            = 0;
140         $this->_selected          = 0;
142         $this->_paper_size        = 0x0;
143         $this->_orientation       = 0x1;
144         $this->_header            = '';
145         $this->_footer            = '';
146         $this->_hcenter           = 0;
147         $this->_vcenter           = 0;
148         $this->_margin_head       = 0.50;
149         $this->_margin_foot       = 0.50;
150         $this->_margin_left       = 0.75;
151         $this->_margin_right      = 0.75;
152         $this->_margin_top        = 1.00;
153         $this->_margin_bottom     = 1.00;
155         $this->_title_rowmin      = false;
156         $this->_title_rowmax      = false;
157         $this->_title_colmin      = false;
158         $this->_title_colmax      = false;
159         $this->_print_rowmin      = false;
160         $this->_print_rowmax      = false;
161         $this->_print_colmin      = false;
162         $this->_print_colmax      = false;
164         $this->_print_gridlines   = 1;
165         $this->_screen_gridlines  = 1;
166         $this->_print_headers     = 0;
168         $this->_fit_page          = 0;
169         $this->_fit_width         = 0;
170         $this->_fit_height        = 0;
172         $this->_hbreaks           = array();
173         $this->_vbreaks           = array();
175         $this->_protect           = 0;
176         $this->_password          = false;
178         $this->_col_sizes         = array();
179         $this->_row_sizes         = array();
181         $this->_col_formats       = array();
182         $this->_row_formats       = array();
184         $this->_zoom              = 100;
185         $this->_print_scale       = 100;
187         $this->_initialize();
188     }
190 ###############################################################################
192 # _initialize()
194 # Open a tmp file to store the majority of the Worksheet data. If this fails,
195 # for example due to write permissions, store the data in memory. This can be
196 # slow for large files.
198 function _initialize() {
200     # Open tmp file for storing Worksheet data.
201     $fh=fopen(tempnam($this->_tempdir, "php_writeexcel"), "w+b");
203     if ($fh) {
204         # Store filehandle
205         $this->_filehandle = $fh;
206     } else {
207         # If tempfile() failed store data in memory
208         $this->_using_tmpfile = 0;
210         if ($this->_index == 0) {
211             $dir = $this->_tempdir;
213 //todo            warn "Unable to create temp files in $dir. Refer to set_tempdir()".
214 //                 " in the Spreadsheet::WriteExcel documentation.\n" ;
215         }
216     }
219     /*
220      * Add data to the beginning of the workbook (note the reverse order)
221      * and to the end of the workbook.
222      */
223     function _close($sheetnames) {
225         ///////////////////////////////
226         // Prepend in reverse order!!
227         //
229         $this->_store_dimensions();        // Prepend the sheet dimensions
230         $this->_store_password();          // Prepend the sheet password
231         $this->_store_protect();           // Prepend the sheet protection
232         $this->_store_setup();             // Prepend the page setup
233         $this->_store_margin_bottom();     // Prepend the bottom margin
234         $this->_store_margin_top();        // Prepend the top margin
235         $this->_store_margin_right();      // Prepend the right margin
236         $this->_store_margin_left();       // Prepend the left margin
237         $this->_store_vcenter();           // Prepend the page vertical
238                                            // centering
239         $this->_store_hcenter();           // Prepend the page horizontal
240                                            // centering
241         $this->_store_footer();            // Prepend the page footer
242         $this->_store_header();            // Prepend the page header
243         $this->_store_vbreak();            // Prepend the vertical page breaks
244         $this->_store_hbreak();            // Prepend the horizontal
245                                            // page breaks
246         $this->_store_wsbool();            // Prepend WSBOOL
247         $this->_store_gridset();           // Prepend GRIDSET
248         $this->_store_print_gridlines();   // Prepend PRINTGRIDLINES
249         $this->_store_print_headers();     // Prepend PRINTHEADERS
251         // Prepend EXTERNSHEET references
252         $num_sheets = sizeof($sheetnames);
253         for ($i = $num_sheets; $i > 0; $i--) {
254             $sheetname = $sheetnames[$i-1];
255             $this->_store_externsheet($sheetname);
256         }
258         $this->_store_externcount($num_sheets);   // Prepend the EXTERNCOUNT
259                                                   // of external references.
261         // Prepend the COLINFO records if they exist
262         if (sizeof($this->_colinfo)>0){
263             while (sizeof($this->_colinfo)>0) {
264                 $arrayref = array_pop ($this->_colinfo);
265                 $this->_store_colinfo($arrayref);
266             }
267             $this->_store_defcol();
268         }
270         $this->_store_bof(0x0010);    // Prepend the BOF record
272         //
273         // End of prepend. Read upwards from here.
274         ////////////////////////////////////////////
276         // Append
277         $this->_store_window2();
278         $this->_store_zoom();
280         if (sizeof($this->_panes)>0) {
281             $this->_store_panes($this->_panes);
282         }
284         $this->_store_selection($this->_selection);
285         $this->_store_eof();
286     }
288     /*
289      * Retrieve the worksheet name.
290      */
291     function get_name() {
292         return $this->_name;
293     }
295 ###############################################################################
297 # get_data().
299 # Retrieves data from memory in one chunk, or from disk in $buffer
300 # sized chunks.
302 function get_data() {
304     $buffer = 4096;
306     # Return data stored in memory
307     if ($this->_data!==false) {
308         $tmp           = $this->_data;
309         $this->_data=false;
310         $fh         = $this->_filehandle;
311         if ($this->_using_tmpfile) {
312             fseek($fh, 0, SEEK_SET);
313         }
315         if ($this->_debug) {
316             print "*** worksheet::get_data() called (1):";
317             for ($c=0;$c<strlen($tmp);$c++) {
318                 if ($c%16==0) {
319                     print "\n";
320                 }
321                 printf("%02X ", ord($tmp[$c]));
322             }
323             print "\n";
324         }
326         return $tmp;
327     }
329     # Return data stored on disk
330     if ($this->_using_tmpfile) {
331         if ($tmp=fread($this->_filehandle, $buffer)) {
333             if ($this->_debug) {
334                 print "*** worksheet::get_data() called (2):";
335                 for ($c=0;$c<strlen($tmp);$c++) {
336                     if ($c%16==0) {
337                         print "\n";
338                     }
339                     printf("%02X ", ord($tmp[$c]));
340                 }
341                 print "\n";
342             }
344             return $tmp;
345         }
346     }
348     # No data to return
349     return false;
352     /*
353      * Set this worksheet as a selected worksheet, i.e. the worksheet has
354      * its tab highlighted.
355      */
356     function select() {
357         $this->_selected = 1;
358     }
360     /*
361      * Set this worksheet as the active worksheet, i.e. the worksheet
362      * that is displayed when the workbook is opened. Also set it as
363      * selected.
364      */
365     function activate() {
366         $this->_selected = 1;
367         $this->_activesheet = $this->_index;
368     }
370     /*
371      * Set this worksheet as the first visible sheet. This is necessary
372      * when there are a large number of worksheets and the activated
373      * worksheet is not visible on the screen.
374      */
375     function set_first_sheet() {
376         $this->_firstsheet = $this->_index;
377     }
379     /*
380      * Set the worksheet protection flag to prevent accidental modification
381      * and to hide formulas if the locked and hidden format properties have
382      * been set.
383      */
384     function protect($password) {
385         $this->_protect   = 1;
386         $this->_password  = $this->_encode_password($password);
387     }
389 ###############################################################################
391 # set_column($firstcol, $lastcol, $width, $format, $hidden)
393 # Set the width of a single column or a range of column.
394 # See also: _store_colinfo
396 function set_column() {
398     $_=func_get_args();
400     $cell = $_[0];
402     # Check for a cell reference in A1 notation and substitute row and column
403     if (preg_match('/^\D/', $cell)) {
404         $_ = $this->_substitute_cellref($_);
405     }
407     array_push($this->_colinfo, $_);
409     # Store the col sizes for use when calculating image vertices taking
410     # hidden columns into account. Also store the column formats.
411     #
412     if (sizeof($_)<3) {
413         # Ensure at least $firstcol, $lastcol and $width
414         return;
415     }
417     $width  = $_[4] ? 0 : $_[2]; # Set width to zero if column is hidden
418     $format = $_[3];
420     list($firstcol, $lastcol) = $_;
422     for ($col=$firstcol;$col<=$lastcol;$col++) {
423         $this->_col_sizes[$col]   = $width;
424         if ($format) {
425             $this->_col_formats[$col] = $format;
426         }
427     }
430 ###############################################################################
432 # set_selection()
434 # Set which cell or cells are selected in a worksheet: see also the
435 # function _store_selection
437 function set_selection() {
439     $_=func_get_args();
441     # Check for a cell reference in A1 notation and substitute row and column
442     if (preg_match('/^\D/', $_[0])) {
443         $_ = $this->_substitute_cellref($_);
444     }
446     $this->_selection = $_;
449 ###############################################################################
451 # freeze_panes()
453 # Set panes and mark them as frozen. See also _store_panes().
455 function freeze_panes() {
457     $_=func_get_args();
459     # Check for a cell reference in A1 notation and substitute row and column
460     if (preg_match('/^\D/', $_[0])) {
461         $_ = $this->_substitute_cellref($_);
462     }
464     $this->_frozen = 1;
465     $this->_panes  = $_;
468 ###############################################################################
470 # thaw_panes()
472 # Set panes and mark them as unfrozen. See also _store_panes().
474 function thaw_panes() {
476     $_=func_get_args();
478     $this->_frozen = 0;
479     $this->_panes  = $_;
482     /*
483      * Set the page orientation as portrait.
484      */
485     function set_portrait() {
486         $this->_orientation = 1;
487     }
489     /*
490      * Set the page orientation as landscape.
491      */
492     function set_landscape() {
493         $this->_orientation = 0;
494     }
496     /*
497      * Set the paper type. Ex. 1 = US Letter, 9 = A4
498      */
499     function set_paper($type) {
500         $this->_paper_size = $type;
501     }
503     /*
504      * Set the page header caption and optional margin.
505      */
506     function set_header($string, $margin) {
508         if (strlen($string) >= 255) {
509             trigger_error("Header string must be less than 255 characters",
510                           E_USER_WARNING);
511             return;
512         }
514         $this->_header      = $string;
515         $this->_margin_head = $margin;
516     }
518     /*
519      * Set the page footer caption and optional margin.
520      */
521     function set_footer($string, $margin) {
522         if (strlen($string) >= 255) {
523             trigger_error("Footer string must be less than 255 characters",
524                           E_USER_WARNING);
525             return;
526         }
528         $this->_footer      = $string;
529         $this->_margin_foot = $margin;
530     }
532     /*
533      * Center the page horizontally.
534      */
535     function center_horizontally($hcenter=1) {
536         $this->_hcenter = $hcenter;
537     }
539     /*
540      * Center the page horizontally.
541      */
542     function center_vertically($vcenter=1) {
543         $this->_vcenter = $vcenter;
544     }
546     /*
547      * Set all the page margins to the same value in inches.
548      */
549     function set_margins($margin) {
550         $this->set_margin_left($margin);
551         $this->set_margin_right($margin);
552         $this->set_margin_top($margin);
553         $this->set_margin_bottom($margin);
554     }
556     /*
557      * Set the left and right margins to the same value in inches.
558      */
559     function set_margins_LR($margin) {
560         $this->set_margin_left($margin);
561         $this->set_margin_right($margin);
562     }
564     /*
565      * Set the top and bottom margins to the same value in inches.
566      */
567     function set_margins_TB($margin) {
568         $this->set_margin_top($margin);
569         $this->set_margin_bottom($margin);
570     }
572     /*
573      * Set the left margin in inches.
574      */
575     function set_margin_left($margin=0.75) {
576         $this->_margin_left = $margin;
577     }
579     /*
580      * Set the right margin in inches.
581      */
582     function set_margin_right($margin=0.75) {
583         $this->_margin_right = $margin;
584     }
586     /*
587      * Set the top margin in inches.
588      */
589     function set_margin_top($margin=1.00) {
590         $this->_margin_top = $margin;
591     }
593     /*
594      * Set the bottom margin in inches.
595      */
596     function set_margin_bottom($margin=1.00) {
597         $this->_margin_bottom = $margin;
598     }
600 ###############################################################################
602 # repeat_rows($first_row, $last_row)
604 # Set the rows to repeat at the top of each printed page. See also the
605 # _store_name_xxxx() methods in Workbook.pm.
607 function repeat_rows() {
609     $_=func_get_args();
611     $this->_title_rowmin  = $_[0];
612     $this->_title_rowmax  = isset($_[1]) ? $_[1] : $_[0]; # Second row is optional
615 ###############################################################################
617 # repeat_columns($first_col, $last_col)
619 # Set the columns to repeat at the left hand side of each printed page.
620 # See also the _store_names() methods in Workbook.pm.
622 function repeat_columns() {
624     $_=func_get_args();
626     # Check for a cell reference in A1 notation and substitute row and column
627     if (preg_match('/^\D/', $_[0])) {
628         $_ = $this->_substitute_cellref($_);
629     }
631     $this->_title_colmin  = $_[0];
632     $this->_title_colmax  = isset($_[1]) ? $_[1] : $_[0]; # Second col is optional
635 ###############################################################################
637 # print_area($first_row, $first_col, $last_row, $last_col)
639 # Set the area of each worksheet that will be printed. See also the
640 # _store_names() methods in Workbook.pm.
642 function print_area() {
644     $_=func_get_args();
646     # Check for a cell reference in A1 notation and substitute row and column
647     if (preg_match('/^\D/', $_[0])) {
648         $_ = $this->_substitute_cellref($_);
649     }
651     if (sizeof($_) != 4) {
652         # Require 4 parameters
653         return;
654     }
656     $this->_print_rowmin = $_[0];
657     $this->_print_colmin = $_[1];
658     $this->_print_rowmax = $_[2];
659     $this->_print_colmax = $_[3];
662     /*
663      * Set the option to hide gridlines on the screen and the printed page.
664      * There are two ways of doing this in the Excel BIFF format: The first
665      * is by setting the DspGrid field of the WINDOW2 record, this turns off
666      * the screen and subsequently the print gridline. The second method is
667      * to via the PRINTGRIDLINES and GRIDSET records, this turns off the
668      * printed gridlines only. The first method is probably sufficient for
669      * most cases. The second method is supported for backwards compatibility.
670      */
671     function hide_gridlines($option=1) {
672         if ($option == 0) {
673             $this->_print_gridlines  = 1; # 1 = display, 0 = hide
674             $this->_screen_gridlines = 1;
675         } elseif ($option == 1) {
676             $this->_print_gridlines  = 0;
677             $this->_screen_gridlines = 1;
678         } else {
679             $this->_print_gridlines  = 0;
680             $this->_screen_gridlines = 0;
681         }
682     }
684     /*
685      * Set the option to print the row and column headers on the printed page.
686      * See also the _store_print_headers() method below.
687      */
688     function print_row_col_headers($headers=1) {
689         $this->_print_headers = $headers;
690     }
692     /*
693      * Store the vertical and horizontal number of pages that will define
694      * the maximum area printed. See also _store_setup() and _store_wsbool()
695      * below.
696      */
697     function fit_to_pages($width, $height) {
698         $this->_fit_page   = 1;
699         $this->_fit_width  = $width;
700         $this->_fit_height = $height;
701     }
703     /*
704      * Store the horizontal page breaks on a worksheet.
705      */
706     function set_h_pagebreaks($breaks) {
707         $this->_hbreaks=array_merge($this->_hbreaks, $breaks);
708     }
710     /*
711      * Store the vertical page breaks on a worksheet.
712      */
713     function set_v_pagebreaks($breaks) {
714         $this->_vbreaks=array_merge($this->_vbreaks, $breaks);
715     }
717     /*
718      * Set the worksheet zoom factor.
719      */
720     function set_zoom($scale=100) {
721         // Confine the scale to Excel's range
722         if ($scale < 10 || $scale > 400) {
723             trigger_error("Zoom factor $scale outside range: ".
724                           "10 <= zoom <= 400", E_USER_WARNING);
725             $scale = 100;
726         }
728         $this->_zoom = $scale;
729     }
731     /*
732      * Set the scale factor for the printed page.
733      */
734     function set_print_scale($scale=100) {
735         // Confine the scale to Excel's range
736         if ($scale < 10 || $scale > 400) {
737             trigger_error("Print scale $scale outside range: ".
738                           "10 <= zoom <= 400", E_USER_WARNING);
739             $scale = 100;
740         }
742         // Turn off "fit to page" option
743         $this->_fit_page = 0;
745         $this->_print_scale = $scale;
746     }
748 ###############################################################################
750 # write($row, $col, $token, $format)
752 # Parse $token call appropriate write method. $row and $column are zero
753 # indexed. $format is optional.
755 # Returns: return value of called subroutine
757 function write() {
759     $_=func_get_args();
761     # Check for a cell reference in A1 notation and substitute row and column
762     if (preg_match('/^\D/', $_[0])) {
763         $_ = $this->_substitute_cellref($_);
764     }
766     $token = $_[2];
768     # Match an array ref.
769     if (is_array($token)) {
770         return call_user_method_array('write_row', $this, $_);
771     }
773     # Match number
774     if (preg_match('/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/', $token)) {
775         return call_user_method_array('write_number', $this, $_);
776     }
777     # Match http, https or ftp URL
778     elseif (preg_match('|^[fh]tt?ps?://|', $token)) {
779         return call_user_method_array('write_url', $this, $_);
780     }
781     # Match mailto:
782     elseif (preg_match('/^mailto:/', $token)) {
783         return call_user_method_array('write_url', $this, $_);
784     }
785     # Match internal or external sheet link
786     elseif (preg_match('[^(?:in|ex)ternal:]', $token)) {
787         return call_user_method_array('write_url', $this, $_);
788     }
789     # Match formula
790     elseif (preg_match('/^=/', $token)) {
791         return call_user_method_array('write_formula', $this, $_);
792     }
793     # Match blank
794     elseif ($token == '') {
795         array_splice($_, 2, 1); # remove the empty string from the parameter list
796         return call_user_method_array('write_blank', $this, $_);
797     }
798     # Default: match string
799     else {
800         return call_user_method_array('write_string', $this, $_);
801     }
804 ###############################################################################
806 # write_row($row, $col, $array_ref, $format)
808 # Write a row of data starting from ($row, $col). Call write_col() if any of
809 # the elements of the array ref are in turn array refs. This allows the writing
810 # of 1D or 2D arrays of data in one go.
812 # Returns: the first encountered error value or zero for no errors
814 function write_row() {
816     $_=func_get_args();
818     # Check for a cell reference in A1 notation and substitute row and column
819     if (preg_match('/^\D/', $_[0])) {
820         $_ = $this->_substitute_cellref($_);
821     }
823     # Catch non array refs passed by user.
824     if (!is_array($_[2])) {
825         trigger_error("Not an array ref in call to write_row()!", E_USER_ERROR);
826     }
828     list($row, $col, $tokens)=array_splice($_, 0, 3);
829     $options = $_[0];
830     $error   = 0;
832     foreach ($tokens as $token) {
834         # Check for nested arrays
835         if (is_array($token)) {
836             $ret = $this->write_col($row, $col, $token, $options);
837         } else {
838             $ret = $this->write    ($row, $col, $token, $options);
839         }
841         # Return only the first error encountered, if any.
842         $error = $error || $ret;
843         $col++;
844     }
846     return $error;
849 ###############################################################################
851 # _XF()
853 # Returns an index to the XF record in the workbook.
854 # TODO
856 # Note: this is a function, not a method.
858 function _XF($row=false, $col=false, $format=false) {
860     if ($format) {
861         return $format->get_xf_index();
862     } elseif (isset($this->_row_formats[$row])) {
863         return $this->_row_formats[$row]->get_xf_index();
864     } elseif (isset($this->_col_formats[$col])) {
865         return $this->_col_formats[$col]->get_xf_index();
866     } else {
867         return 0x0F;
868     }
871 ###############################################################################
873 # write_col($row, $col, $array_ref, $format)
875 # Write a column of data starting from ($row, $col). Call write_row() if any of
876 # the elements of the array ref are in turn array refs. This allows the writing
877 # of 1D or 2D arrays of data in one go.
879 # Returns: the first encountered error value or zero for no errors
881 function write_col() {
883     $_=func_get_args();
885     # Check for a cell reference in A1 notation and substitute row and column
886     if (preg_match('/^\D/', $_[0])) {
887         $_ = $this->_substitute_cellref($_);
888     }
890     # Catch non array refs passed by user.
891     if (!is_array($_[2])) {
892         trigger_error("Not an array ref in call to write_row()!", E_USER_ERROR);
893     }
895     $row     = array_shift($_);
896     $col     = array_shift($_);
897     $tokens  = array_shift($_);
898     $options = $_;
900     $error   = 0;
902     foreach ($tokens as $token) {
904         # write() will deal with any nested arrays
905         $ret = $this->write($row, $col, $token, $options);
907         # Return only the first error encountered, if any.
908         $error = $error || $ret;
909         $row++;
910     }
912     return $error;
915 ###############################################################################
916 ###############################################################################
918 # Internal methods
921 ###############################################################################
923 # _append(), overloaded.
925 # Store Worksheet data in memory using the base class _append() or to a
926 # temporary file, the default.
928 function _append($data) {
930     if (func_num_args()>1) {
931         trigger_error("writeexcel_worksheet::_append() ".
932                       "called with more than one argument", E_USER_ERROR);
933     }
935     if ($this->_using_tmpfile) {
937         if ($this->_debug) {
938             print "worksheet::_append() called:";
939             for ($c=0;$c<strlen($data);$c++) {
940                 if ($c%16==0) {
941                     print "\n";
942                 }
943                 printf("%02X ", ord($data[$c]));
944             }
945             print "\n";
946         }
948         # Add CONTINUE records if necessary
949         if (strlen($data) > $this->_limit) {
950             $data = $this->_add_continue($data);
951         }
953         fputs($this->_filehandle, $data);
954         $this->_datasize += strlen($data);
955     } else {
956         parent::_append($data);
957     }
960 ###############################################################################
962 # _substitute_cellref()
964 # Substitute an Excel cell reference in A1 notation for  zero based row and
965 # column values in an argument list.
967 # Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
969 // Exactly one array must be passed!
970 function _substitute_cellref($_) {
971     $cell = strtoupper(array_shift($_));
973     # Convert a column range: 'A:A' or 'B:G'
974     if (preg_match('/([A-I]?[A-Z]):([A-I]?[A-Z])/', $cell, $reg)) {
975         list($dummy, $col1) =  $this->_cell_to_rowcol($reg[1] .'1'); # Add a dummy row
976         list($dummy, $col2) =  $this->_cell_to_rowcol($reg[2] .'1'); # Add a dummy row
977         return array_merge(array($col1, $col2), $_);
978     }
980     # Convert a cell range: 'A1:B7'
981     if (preg_match('/\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)/', $cell, $reg)) {
982         list($row1, $col1) =  $this->_cell_to_rowcol($reg[1]);
983         list($row2, $col2) =  $this->_cell_to_rowcol($reg[2]);
984         return array_merge(array($row1, $col1, $row2, $col2), $_);
985     }
987     # Convert a cell reference: 'A1' or 'AD2000'
988     if (preg_match('/\$?([A-I]?[A-Z]\$?\d+)/', $cell, $reg)) {
989         list($row1, $col1) =  $this->_cell_to_rowcol($reg[1]);
990         return array_merge(array($row1, $col1), $_);
992     }
994     trigger_error("Unknown cell reference $cell", E_USER_ERROR);
997 ###############################################################################
999 # _cell_to_rowcol($cell_ref)
1001 # Convert an Excel cell reference in A1 notation to a zero based row and column
1002 # reference; converts C1 to (0, 2).
1004 # Returns: row, column
1006 # TODO use functions in Utility.pm
1008 function _cell_to_rowcol($cell) {
1010     preg_match('/\$?([A-I]?[A-Z])\$?(\d+)/', $cell, $reg);
1012     $col     = $reg[1];
1013     $row     = $reg[2];
1015     # Convert base26 column string to number
1016     # All your Base are belong to us.
1017     $chars = preg_split('//', $col, -1, PREG_SPLIT_NO_EMPTY);
1018     $expn  = 0;
1019     $col      = 0;
1021     while (sizeof($chars)) {
1022         $char = array_pop($chars); # LS char first
1023         $col += (ord($char) -ord('A') +1) * pow(26, $expn);
1024         $expn++;
1025     }
1027     # Convert 1-index to zero-index
1028     $row--;
1029     $col--;
1031     return array($row, $col);
1034     /*
1035      * This is an internal method that is used to filter elements of the
1036      * array of pagebreaks used in the _store_hbreak() and _store_vbreak()
1037      * methods. It:
1038      *   1. Removes duplicate entries from the list.
1039      *   2. Sorts the list.
1040      *   3. Removes 0 from the list if present.
1041      */
1042     function _sort_pagebreaks($breaks) {
1043         // Hash slice to remove duplicates
1044         foreach ($breaks as $break) {
1045             $hash["$break"]=1;
1046         }
1048         // Numerical sort
1049         $breaks=array_keys($hash);
1050         sort($breaks, SORT_NUMERIC);
1052         // Remove zero
1053         if ($breaks[0] == 0) {
1054             array_shift($breaks);
1055         }
1057         // 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
1058         // It is slightly higher in Excel 97/200, approx. 1026
1059         if (sizeof($breaks) > 1000) {
1060             array_splice($breaks, 1000);
1061         }
1063         return $breaks;
1064     }
1066     /*
1067      * Based on the algorithm provided by Daniel Rentz of OpenOffice.
1068      */
1069     function _encode_password($plaintext) {
1070         $chars=preg_split('//', $plaintext, -1, PREG_SPLIT_NO_EMPTY);
1071         $count=sizeof($chars);
1073         for ($c=0;$c<sizeof($chars);$c++) {
1074             $char=&$chars[$c];
1075             $char    = ord($char) << ++$i;
1076             $low_15  = $char & 0x7fff;
1077             $high_15 = $char & 0x7fff << 15;
1078             $high_15 = $high_15 >> 15;
1079             $char    = $low_15 | $high_15;
1080         }
1082         $password = 0x0000;
1084         foreach ($chars as $char) {
1085             $password ^= $char;
1086         }
1088         $password ^= $count;
1089         $password ^= 0xCE4B;
1091         return $password;
1092     }
1094 ###############################################################################
1095 ###############################################################################
1097 # BIFF RECORDS
1100 ###############################################################################
1102 # write_number($row, $col, $num, $format)
1104 # Write a double to the specified row and column (zero indexed).
1105 # An integer can be written as a double. Excel will display an
1106 # integer. $format is optional.
1108 # Returns  0 : normal termination
1109 #         -1 : insufficient number of arguments
1110 #         -2 : row or column out of range
1112 function write_number() {
1114     $_=func_get_args();
1116     # Check for a cell reference in A1 notation and substitute row and column
1117     if (preg_match('/^\D/', $_[0])) {
1118         $_ = $this->_substitute_cellref($_);
1119     }
1121     # Check the number of args
1122     if (sizeof($_) < 3) {
1123         return -1;
1124     }
1126     $record  = 0x0203;                        # Record identifier
1127     $length  = 0x000E;                        # Number of bytes to follow
1129     $row     = $_[0];                         # Zero indexed row
1130     $col     = $_[1];                         # Zero indexed column
1131     $num     = $_[2];
1132 //!!!
1133     $xf      = $this->_XF($row, $col, $_[3]); # The cell format
1135     # Check that row and col are valid and store max and min values
1136     if ($row >= $this->_xls_rowmax) { return -2; }
1137     if ($col >= $this->_xls_colmax) { return -2; }
1138     if ($row <  $this->_dim_rowmin) { $this->_dim_rowmin = $row; }
1139     if ($row >  $this->_dim_rowmax) { $this->_dim_rowmax = $row; }
1140     if ($col <  $this->_dim_colmin) { $this->_dim_colmin = $col; }
1141     if ($col >  $this->_dim_colmax) { $this->_dim_colmax = $col; }
1143     $header    = pack("vv",  $record, $length);
1144     $data      = pack("vvv", $row, $col, $xf);
1145     $xl_double = pack("d",   $num);
1147     if ($this->_byte_order) {
1148 //TODO
1149         $xl_double = strrev($xl_double);
1150     }
1152     $this->_append($header . $data . $xl_double);
1154     return 0;
1157 ###############################################################################
1159 # write_string ($row, $col, $string, $format)
1161 # Write a string to the specified row and column (zero indexed).
1162 # NOTE: there is an Excel 5 defined limit of 255 characters.
1163 # $format is optional.
1164 # Returns  0 : normal termination
1165 #         -1 : insufficient number of arguments
1166 #         -2 : row or column out of range
1167 #         -3 : long string truncated to 255 chars
1169 function write_string() {
1171     $_=func_get_args();
1173     # Check for a cell reference in A1 notation and substitute row and column
1174     if (preg_match('/^\D/', $_[0])) {
1175         $_ = $this->_substitute_cellref($_);
1176     }
1178     # Check the number of args
1179     if (sizeof($_) < 3) {
1180         return -1;
1181     }
1183     $record  = 0x0204;                        # Record identifier
1184     $length  = 0x0008 + strlen($_[2]);        # Bytes to follow
1186     $row     = $_[0];                         # Zero indexed row
1187     $col     = $_[1];                         # Zero indexed column
1188     $strlen  = strlen($_[2]);
1189     $str     = $_[2];
1190     $xf      = $this->_XF($row, $col, $_[3]); # The cell format
1192     $str_error = 0;
1194     # Check that row and col are valid and store max and min values
1195     if ($row >= $this->_xls_rowmax) { return -2; }
1196     if ($col >= $this->_xls_colmax) { return -2; }
1197     if ($row <  $this->_dim_rowmin) { $this->_dim_rowmin = $row; }
1198     if ($row >  $this->_dim_rowmax) { $this->_dim_rowmax = $row; }
1199     if ($col <  $this->_dim_colmin) { $this->_dim_colmin = $col; }
1200     if ($col >  $this->_dim_colmax) { $this->_dim_colmax = $col; }
1202     if ($strlen > $this->_xls_strmax) { # LABEL must be < 255 chars
1203         $str       = substr($str, 0, $this->_xls_strmax);
1204         $length    = 0x0008 + $this->_xls_strmax;
1205         $strlen    = $this->_xls_strmax;
1206         $str_error = -3;
1207     }
1209     $header    = pack("vv",   $record, $length);
1210     $data      = pack("vvvv", $row, $col, $xf, $strlen);
1212     $this->_append($header . $data . $str);
1214     return $str_error;
1217 ###############################################################################
1219 # write_blank($row, $col, $format)
1221 # Write a blank cell to the specified row and column (zero indexed).
1222 # A blank cell is used to specify formatting without adding a string
1223 # or a number.
1225 # A blank cell without a format serves no purpose. Therefore, we don't write
1226 # a BLANK record unless a format is specified. This is mainly an optimisation
1227 # for the write_row() and write_col() methods.
1229 # Returns  0 : normal termination (including no format)
1230 #         -1 : insufficient number of arguments
1231 #         -2 : row or column out of range
1233 function write_blank() {
1235     $_=func_get_args();
1237     # Check for a cell reference in A1 notation and substitute row and column
1238     if (preg_match('/^\D/', $_[0])) {
1239         $_ = $this->_substitute_cellref($_);
1240     }
1242     # Check the number of args
1243     if (sizeof($_) < 2) {
1244         return -1;
1245     }
1247     # Don't write a blank cell unless it has a format
1248     if (!isset($_[2])) {
1249         return 0;
1250     }
1252     $record  = 0x0201;                        # Record identifier
1253     $length  = 0x0006;                        # Number of bytes to follow
1255     $row     = $_[0];                         # Zero indexed row
1256     $col     = $_[1];                         # Zero indexed column
1257     $xf      = $this->_XF($row, $col, $_[2]); # The cell format
1259     # Check that row and col are valid and store max and min values
1260     if ($row >= $this->_xls_rowmax) { return -2; }
1261     if ($col >= $this->_xls_colmax) { return -2; }
1262     if ($row <  $this->_dim_rowmin) { $this->_dim_rowmin = $row; }
1263     if ($row >  $this->_dim_rowmax) { $this->_dim_rowmax = $row; }
1264     if ($col <  $this->_dim_colmin) { $this->_dim_colmin = $col; }
1265     if ($col >  $this->_dim_colmax) { $this->_dim_colmax = $col; }
1267     $header    = pack("vv",  $record, $length);
1268     $data      = pack("vvv", $row, $col, $xf);
1270     $this->_append($header . $data);
1272     return 0;
1275 ###############################################################################
1277 # write_formula($row, $col, $formula, $format)
1279 # Write a formula to the specified row and column (zero indexed).
1280 # The textual representation of the formula is passed to the parser in
1281 # Formula.pm which returns a packed binary string.
1283 # $format is optional.
1285 # Returns  0 : normal termination
1286 #         -1 : insufficient number of arguments
1287 #         -2 : row or column out of range
1289 function write_formula() {
1291     $_=func_get_args();
1293     # Check for a cell reference in A1 notation and substitute row and column
1294     if (preg_match('/^\D/', $_[0])) {
1295         $_ = $this->_substitute_cellref($_);
1296     }
1298     # Check the number of args
1299     if (sizeof($_) < 3) {
1300         return -1;
1301     }
1303     $record    = 0x0006;     # Record identifier
1304     $length=0;                 # Bytes to follow
1306     $row       = $_[0];      # Zero indexed row
1307     $col       = $_[1];      # Zero indexed column
1308     $formula   = $_[2];      # The formula text string
1310     # Excel normally stores the last calculated value of the formula in $num.
1311     # Clearly we are not in a position to calculate this a priori. Instead
1312     # we set $num to zero and set the option flags in $grbit to ensure
1313     # automatic calculation of the formula when the file is opened.
1314     #
1315     $xf        = $this->_XF($row, $col, $_[3]); # The cell format
1316     $num       = 0x00;                          # Current value of formula
1317     $grbit     = 0x03;                          # Option flags
1318     $chn       = 0x0000;                        # Must be zero
1320     # Check that row and col are valid and store max and min values
1321     if ($row >= $this->_xls_rowmax) { return -2; }
1322     if ($col >= $this->_xls_colmax) { return -2; }
1323     if ($row <  $this->_dim_rowmin) { $this->_dim_rowmin = $row; }
1324     if ($row >  $this->_dim_rowmax) { $this->_dim_rowmax = $row; }
1325     if ($col <  $this->_dim_colmin) { $this->_dim_colmin = $col; }
1326     if ($col >  $this->_dim_colmax) { $this->_dim_colmax = $col; }
1328     # Strip the = sign at the beginning of the formula string
1329     $formula = preg_replace('/^=/', "", $formula);
1331     # Parse the formula using the parser in Formula.pm
1332     $parser =& $this->_parser;
1333     $formula   = $parser->parse_formula($formula);
1335     $formlen = strlen($formula); # Length of the binary string
1336     $length     = 0x16 + $formlen;  # Length of the record data
1338     $header    = pack("vv",      $record, $length);
1339     $data      = pack("vvvdvVv", $row, $col, $xf, $num,
1340                                   $grbit, $chn, $formlen);
1342     $this->_append($header . $data . $formula);
1344     return 0;
1347 ###############################################################################
1349 # write_url($row, $col, $url, $string, $format)
1351 # Write a hyperlink. This is comprised of two elements: the visible label and
1352 # the invisible link. The visible label is the same as the link unless an
1353 # alternative string is specified. The label is written using the
1354 # write_string() method. Therefore the 255 characters string limit applies.
1355 # $string and $format are optional and their order is interchangeable.
1357 # The hyperlink can be to a http, ftp, mail, internal sheet, or external
1358 # directory url.
1360 # Returns  0 : normal termination
1361 #         -1 : insufficient number of arguments
1362 #         -2 : row or column out of range
1363 #         -3 : long string truncated to 255 chars
1365 function write_url() {
1367     $_=func_get_args();
1369     # Check for a cell reference in A1 notation and substitute row and column
1370     if (preg_match('/^\D/', $_[0])) {
1371         $_ = $this->_substitute_cellref($_);
1372     }
1374     # Check the number of args
1375     if (sizeof($_) < 3) {
1376         return -1;
1377     }
1379     # Add start row and col to arg list
1380     return call_user_method_array('write_url_range', $this,
1381                                   array_merge(array($_[0], $_[1]), $_));
1384 ###############################################################################
1386 # write_url_range($row1, $col1, $row2, $col2, $url, $string, $format)
1388 # This is the more general form of write_url(). It allows a hyperlink to be
1389 # written to a range of cells. This function also decides the type of hyperlink
1390 # to be written. These are either, Web (http, ftp, mailto), Internal
1391 # (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').
1393 # See also write_url() above for a general description and return values.
1395 function write_url_range() {
1397     $_=func_get_args();
1399     # Check for a cell reference in A1 notation and substitute row and column
1400     if (preg_match('/^\D/', $_[0])) {
1401         $_ = $this->_substitute_cellref($_);
1402     }
1404     # Check the number of args
1405     if (sizeof($_) < 5) {
1406         return -1;
1407     }
1409     # Reverse the order of $string and $format if necessary.
1410 //TODO    ($_[5], $_[6]) = ($_[6], $_[5]) if (ref $_[5]);
1412     $url = $_[4];
1414     # Check for internal/external sheet links or default to web link
1415     if (preg_match('[^internal:]', $url)) {
1416         return call_user_method_array('_write_url_internal', $this, $_);
1417     }
1419     if (preg_match('[^external:]', $url)) {
1420         return call_user_method_array('_write_url_external', $this, $_);
1421     }
1423     return call_user_method_array('_write_url_web', $this, $_);
1426 ###############################################################################
1428 # _write_url_web($row1, $col1, $row2, $col2, $url, $string, $format)
1430 # Used to write http, ftp and mailto hyperlinks.
1431 # The link type ($options) is 0x03 is the same as absolute dir ref without
1432 # sheet. However it is differentiated by the $unknown2 data stream.
1434 # See also write_url() above for a general description and return values.
1436 function _write_url_web() {
1438     $_=func_get_args();
1440     $record      = 0x01B8;                       # Record identifier
1441     $length      = 0x00000;                      # Bytes to follow
1443     $row1        = $_[0];                        # Start row
1444     $col1        = $_[1];                        # Start column
1445     $row2        = $_[2];                        # End row
1446     $col2        = $_[3];                        # End column
1447     $url         = $_[4];                        # URL string
1448     if (isset($_[5])) {
1449         $str         = $_[5];                        # Alternative label
1450     }
1451     $xf          = $_[6] ? $_[6] : $this->_url_format;  # The cell format
1453     # Write the visible label using the write_string() method.
1454     if(!isset($str)) {
1455         $str            = $url;
1456     }
1458     $str_error   = $this->write_string($row1, $col1, $str, $xf);
1460     if ($str_error == -2) {
1461         return $str_error;
1462     }
1464     # Pack the undocumented parts of the hyperlink stream
1465     $unknown1    = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
1466     $unknown2    = pack("H*", "E0C9EA79F9BACE118C8200AA004BA90B");
1468     # Pack the option flags
1469     $options     = pack("V", 0x03);
1471     # Convert URL to a null terminated wchar string
1472     $url            = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
1473     $url            = $url . "\0\0\0";
1475     # Pack the length of the URL
1476     $url_len     = pack("V", strlen($url));
1478     # Calculate the data length
1479     $length         = 0x34 + strlen($url);
1481     # Pack the header data
1482     $header      = pack("vv",   $record, $length);
1483     $data        = pack("vvvv", $row1, $row2, $col1, $col2);
1485     # Write the packed data
1486     $this->_append($header.
1487                    $data.
1488                    $unknown1.
1489                    $options.
1490                    $unknown2.
1491                    $url_len.
1492                    $url);
1494     return $str_error;
1497 ###############################################################################
1499 # _write_url_internal($row1, $col1, $row2, $col2, $url, $string, $format)
1501 # Used to write internal reference hyperlinks such as "Sheet1!A1".
1503 # See also write_url() above for a general description and return values.
1505 function _write_url_internal() {
1507     $_=func_get_args();
1509     $record      = 0x01B8;                       # Record identifier
1510     $length      = 0x00000;                      # Bytes to follow
1512     $row1        = $_[0];                        # Start row
1513     $col1        = $_[1];                        # Start column
1514     $row2        = $_[2];                        # End row
1515     $col2        = $_[3];                        # End column
1516     $url         = $_[4];                        # URL string
1517     if (isset($_[5])) {
1518         $str         = $_[5];                        # Alternative label
1519     }
1520     $xf          = $_[6] ? $_[6] : $this->_url_format;  # The cell format
1522     # Strip URL type
1523     $url = preg_replace('s[^internal:]', '', $url);
1525     # Write the visible label
1526     if (!isset($str)) {
1527         $str = $url;
1528     }
1529     $str_error   = $this->write_string($row1, $col1, $str, $xf);
1531     if ($str_error == -2) {
1532         return $str_error;
1533     }
1535     # Pack the undocumented parts of the hyperlink stream
1536     $unknown1    = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
1538     # Pack the option flags
1539     $options     = pack("V", 0x08);
1541     # Convert the URL type and to a null terminated wchar string
1542     $url            = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
1543     $url            = $url . "\0\0\0";
1545     # Pack the length of the URL as chars (not wchars)
1546     $url_len     = pack("V", int(strlen($url)/2));
1548     # Calculate the data length
1549     $length         = 0x24 + strlen($url);
1551     # Pack the header data
1552     $header      = pack("vv",   $record, $length);
1553     $data        = pack("vvvv", $row1, $row2, $col1, $col2);
1555     # Write the packed data
1556     $this->_append($header.
1557                    $data.
1558                    $unknown1.
1559                    $options.
1560                    $url_len.
1561                    $url);
1563     return $str_error;
1566 ###############################################################################
1568 # _write_url_external($row1, $col1, $row2, $col2, $url, $string, $format)
1570 # Write links to external directory names such as 'c:\foo.xls',
1571 # c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
1573 # Note: Excel writes some relative links with the $dir_long string. We ignore
1574 # these cases for the sake of simpler code.
1576 # See also write_url() above for a general description and return values.
1578 function _write_url_external() {
1580     $_=func_get_args();
1582     # Network drives are different. We will handle them separately
1583     # MS/Novell network drives and shares start with \\
1584     if (preg_match('[^external:\\\\]', $_[4])) {
1585         return call_user_method_array('_write_url_external_net', $this, $_);
1586     }
1588     $record      = 0x01B8;                       # Record identifier
1589     $length      = 0x00000;                      # Bytes to follow
1591     $row1        = $_[0];                        # Start row
1592     $col1        = $_[1];                        # Start column
1593     $row2        = $_[2];                        # End row
1594     $col2        = $_[3];                        # End column
1595     $url         = $_[4];                        # URL string
1596     if (isset($_[5])) {
1597         $str         = $_[5];                        # Alternative label
1598     }
1599     $xf          = $_[6] ? $_[6] : $this->_url_format;  # The cell format
1601     # Strip URL type and change Unix dir separator to Dos style (if needed)
1602     #
1603     $url            = preg_replace('[^external:]', '', $url);
1604     $url            = preg_replace('[/]', "\\", $url);
1606     # Write the visible label
1607     if (!isset($str)) {
1608         $str = preg_replace('[\#]', ' - ', $url);
1609     }
1610     $str_error   = $this->write_string($row1, $col1, $str, $xf);
1611     if ($str_error == -2) {
1612         return $str_error;
1613     }
1615     # Determine if the link is relative or absolute:
1616     #   relative if link contains no dir separator, "somefile.xls"
1617     #   relative if link starts with up-dir, "..\..\somefile.xls"
1618     #   otherwise, absolute
1619     #
1620     $absolute    = 0x02; # Bit mask
1622     if (!preg_match('[\\]', $url)) {
1623         $absolute    = 0x00;
1624     }
1626     if (preg_match('[^\.\.\\]', $url)) {
1627         $absolute    = 0x00;
1628     }
1630     # Determine if the link contains a sheet reference and change some of the
1631     # parameters accordingly.
1632     # Split the dir name and sheet name (if it exists)
1633     #
1634     list($dir_long, $sheet) = preg_split('/\#/', $url);
1635     $link_type           = 0x01 | $absolute;
1637 //!!!
1638     if (isset($sheet)) {
1639         $link_type |= 0x08;
1640         $sheet_len  = pack("V", length($sheet) + 0x01);
1641         $sheet      = join("\0", split('', $sheet));
1642         $sheet     .= "\0\0\0";
1643     } else {
1644         $sheet_len   = '';
1645         $sheet       = '';
1646     }
1648     # Pack the link type
1649     $link_type      = pack("V", $link_type);
1652     # Calculate the up-level dir count e.g.. (..\..\..\ == 3)
1653 /* TODO
1654     $up_count    = 0;
1655     $up_count++       while $dir_long =~ s[^\.\.\\][];
1656     $up_count       = pack("v", $up_count);
1657 */
1659     # Store the short dos dir name (null terminated)
1660     $dir_short   = $dir_long . "\0";
1662     # Store the long dir name as a wchar string (non-null terminated)
1663     $dir_long       = join("\0", preg_split('', $dir_long, -1, PREG_SPLIT_NO_EMPTY));
1664     $dir_long       = $dir_long . "\0";
1666     # Pack the lengths of the dir strings
1667     $dir_short_len = pack("V", strlen($dir_short)      );
1668     $dir_long_len  = pack("V", strlen($dir_long)       );
1669     $stream_len    = pack("V", strlen($dir_long) + 0x06);
1671     # Pack the undocumented parts of the hyperlink stream
1672     $unknown1 =pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000'       );
1673     $unknown2 =pack("H*",'0303000000000000C000000000000046'               );
1674     $unknown3 =pack("H*",'FFFFADDE000000000000000000000000000000000000000');
1675     $unknown4 =pack("v",  0x03                                            );
1677     # Pack the main data stream
1678     $data        = pack("vvvv", $row1, $row2, $col1, $col2) .
1679                       $unknown1     .
1680                       $link_type    .
1681                       $unknown2     .
1682                       $up_count     .
1683                       $dir_short_len.
1684                       $dir_short    .
1685                       $unknown3     .
1686                       $stream_len   .
1687                       $dir_long_len .
1688                       $unknown4     .
1689                       $dir_long     .
1690                       $sheet_len    .
1691                       $sheet        ;
1693     # Pack the header data
1694     $length         = strlen($data);
1695     $header      = pack("vv",   $record, $length);
1697     # Write the packed data
1698     $this->_append($header . $data);
1700     return $str_error;
1703 ###############################################################################
1705 # write_url_xxx($row1, $col1, $row2, $col2, $url, $string, $format)
1707 # Write links to external MS/Novell network drives and shares such as
1708 # '//NETWORK/share/foo.xls' and '//NETWORK/share/foo.xls#Sheet1!A1'.
1710 # See also write_url() above for a general description and return values.
1712 function _write_url_external_net() {
1714     $_=func_get_args();
1716     $record      = 0x01B8;                       # Record identifier
1717     $length      = 0x00000;                      # Bytes to follow
1719     $row1        = $_[0];                        # Start row
1720     $col1        = $_[1];                        # Start column
1721     $row2        = $_[2];                        # End row
1722     $col2        = $_[3];                        # End column
1723     $url         = $_[4];                        # URL string
1724     if(isset($_[5])) {
1725          $str         = $_[5];                        # Alternative label
1726     }
1727     $xf          = $_[6] ? $_[6] : $this->_url_format;  # The cell format
1729     # Strip URL type and change Unix dir separator to Dos style (if needed)
1730     #
1731     $url            = preg_replace('[^external:]', "", $url);
1732     $url            = preg_replace('[/]', "\\");
1734     # Write the visible label
1735     if (!isset($str)) {
1736         $str = preg_replace('[\#]', " - ", $url);
1737     }
1739     $str_error   = $this->write_string($row1, $col1, $str, $xf);
1740     if ($str_error == -2) {
1741         return $str_error;
1742     }
1744     # Determine if the link contains a sheet reference and change some of the
1745     # parameters accordingly.
1746     # Split the dir name and sheet name (if it exists)
1747     #
1748     list($dir_long , $sheet) = preg_split('\#', $url);
1749     $link_type           = 0x0103; # Always absolute
1751 //!!!
1752     if (isset($sheet)) {
1753         $link_type |= 0x08;
1754         $sheet_len  = pack("V", strlen($sheet) + 0x01);
1755         $sheet      = join("\0", preg_split("''", $sheet, -1, PREG_SPLIT_NO_EMPTY));
1756         $sheet     .= "\0\0\0";
1757     } else {
1758         $sheet_len   = '';
1759         $sheet       = '';
1760     }
1762     # Pack the link type
1763     $link_type      = pack("V", $link_type);
1765     # Make the string null terminated
1766     $dir_long       = $dir_long . "\0";
1768     # Pack the lengths of the dir string
1769     $dir_long_len  = pack("V", strlen($dir_long));
1771     # Store the long dir name as a wchar string (non-null terminated)
1772     $dir_long       = join("\0", preg_split("''", $dir_long, -1, PREG_SPLIT_NO_EMPTY));
1773     $dir_long       = $dir_long . "\0";
1775     # Pack the undocumented part of the hyperlink stream
1776     $unknown1    = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000');
1778     # Pack the main data stream
1779     $data        = pack("vvvv", $row1, $row2, $col1, $col2) .
1780                       $unknown1     .
1781                       $link_type    .
1782                       $dir_long_len .
1783                       $dir_long     .
1784                       $sheet_len    .
1785                       $sheet        ;
1787     # Pack the header data
1788     $length         = strlen($data);
1789     $header      = pack("vv",   $record, $length);
1791     # Write the packed data
1792     $this->_append($header . $data);
1794     return $str_error;
1797 ###############################################################################
1799 # set_row($row, $height, $XF)
1801 # This method is used to set the height and XF format for a row.
1802 # Writes the  BIFF record ROW.
1804 function set_row() {
1806     $_=func_get_args();
1808     $record      = 0x0208;               # Record identifier
1809     $length      = 0x0010;               # Number of bytes to follow
1811     $rw          = $_[0];                # Row Number
1812     $colMic      = 0x0000;               # First defined column
1813     $colMac      = 0x0000;               # Last defined column
1814     //$miyRw;                              # Row height
1815     $irwMac      = 0x0000;               # Used by Excel to optimise loading
1816     $reserved    = 0x0000;               # Reserved
1817     $grbit       = 0x01C0;               # Option flags. (monkey) see $1 do
1818     //$ixfe;                               # XF index
1819     if (isset($_[2])) {
1820         $format      = $_[2];                # Format object
1821     }
1823     # Check for a format object
1824     if (isset($_[2])) {
1825         $ixfe = $format->get_xf_index();
1826     } else {
1827         $ixfe = 0x0F;
1828     }
1830     # Use set_row($row, undef, $XF) to set XF without setting height
1831     if (isset($_[1])) {
1832         $miyRw = $_[1] *20;
1833     } else {
1834         $miyRw = 0xff;
1835     }
1837     $header   = pack("vv",       $record, $length);
1838     $data     = pack("vvvvvvvv", $rw, $colMic, $colMac, $miyRw,
1839                                  $irwMac,$reserved, $grbit, $ixfe);
1841     $this->_append($header . $data);
1843     # Store the row sizes for use when calculating image vertices.
1844     # Also store the column formats.
1845     #
1846     # Ensure at least $row and $height
1847     if (sizeof($_) < 2) {
1848         return;
1849     }
1851     $this->_row_sizes[$_[0]]  = $_[1];
1852     if (isset($_[2])) {
1853         $this->_row_formats[$_[0]] = $_[2];
1854     }
1857     /*
1858      * Writes Excel DIMENSIONS to define the area in which there is data.
1859      */
1860     function _store_dimensions() {
1861         $record    = 0x0000;               // Record identifier
1862         $length    = 0x000A;               // Number of bytes to follow
1863         $row_min   = $this->_dim_rowmin;   // First row
1864         $row_max   = $this->_dim_rowmax;   // Last row plus 1
1865         $col_min   = $this->_dim_colmin;   // First column
1866         $col_max   = $this->_dim_colmax;   // Last column plus 1
1867         $reserved  = 0x0000;               // Reserved by Excel
1869         $header    = pack("vv",    $record, $length);
1870         $data      = pack("vvvvv", $row_min, $row_max,
1871                                    $col_min, $col_max, $reserved);
1872         $this->_prepend($header . $data);
1873     }
1875     /*
1876      * Write BIFF record Window2.
1877      */
1878     function _store_window2() {
1879         $record         = 0x023E;       // Record identifier
1880         $length         = 0x000A;       // Number of bytes to follow
1882         $grbit          = 0x00B6;       // Option flags
1883         $rwTop          = 0x0000;       // Top row visible in window
1884         $colLeft        = 0x0000;       // Leftmost column visible in window
1885         $rgbHdr         = 0x00000000;   // Row/column heading and gridline
1886                                         // color
1888         // The options flags that comprise $grbit
1889         $fDspFmla       = 0;                          // 0 - bit
1890         $fDspGrid       = $this->_screen_gridlines;   // 1
1891         $fDspRwCol      = 1;                          // 2
1892         $fFrozen        = $this->_frozen;             // 3
1893         $fDspZeros      = 1;                          // 4
1894         $fDefaultHdr    = 1;                          // 5
1895         $fArabic        = 0;                          // 6
1896         $fDspGuts       = 1;                          // 7
1897         $fFrozenNoSplit = 0;                          // 0 - bit
1898         $fSelected      = $this->_selected;           // 1
1899         $fPaged         = 1;                          // 2
1901         $grbit             = $fDspFmla;
1902         $grbit            |= $fDspGrid       << 1;
1903         $grbit            |= $fDspRwCol      << 2;
1904         $grbit            |= $fFrozen        << 3;
1905         $grbit            |= $fDspZeros      << 4;
1906         $grbit            |= $fDefaultHdr    << 5;
1907         $grbit            |= $fArabic        << 6;
1908         $grbit            |= $fDspGuts       << 7;
1909         $grbit            |= $fFrozenNoSplit << 8;
1910         $grbit            |= $fSelected      << 9;
1911         $grbit            |= $fPaged         << 10;
1913         $header  = pack("vv",   $record, $length);
1914         $data    = pack("vvvV", $grbit, $rwTop, $colLeft, $rgbHdr);
1916         $this->_append($header . $data);
1917     }
1919     /*
1920      * Write BIFF record DEFCOLWIDTH if COLINFO records are in use.
1921      */
1922     function _store_defcol() {
1923         $record   = 0x0055;   // Record identifier
1924         $length   = 0x0002;   // Number of bytes to follow
1926         $colwidth = 0x0008;   // Default column width
1928         $header   = pack("vv", $record, $length);
1929         $data     = pack("v",  $colwidth);
1931         $this->_prepend($header . $data);
1932     }
1934 ###############################################################################
1936 # _store_colinfo($firstcol, $lastcol, $width, $format, $hidden)
1938 # Write BIFF record COLINFO to define column widths
1940 # Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
1941 # length record.
1943 function _store_colinfo($_) {
1945     $record   = 0x007D;          # Record identifier
1946     $length   = 0x000B;          # Number of bytes to follow
1948     $colFirst = $_[0] ? $_[0] : 0;      # First formatted column
1949     $colLast  = $_[1] ? $_[1] : 0;      # Last formatted column
1950     $coldx    = $_[2] ? $_[2] : 8.43;   # Col width, 8.43 is Excel default
1952     $coldx       += 0.72;           # Fudge. Excel subtracts 0.72 !?
1953     $coldx       *= 256;            # Convert to units of 1/256 of a char
1955     //$ixfe;                       # XF index
1956     $grbit    = $_[4] || 0;      # Option flags
1957     $reserved = 0x00;            # Reserved
1958     $format   = $_[3];           # Format object
1960     # Check for a format object
1961     if (isset($_[3])) {
1962         $ixfe = $format->get_xf_index();
1963     } else {
1964         $ixfe = 0x0F;
1965     }
1967     $header   = pack("vv",     $record, $length);
1968     $data     = pack("vvvvvC", $colFirst, $colLast, $coldx,
1969                                $ixfe, $grbit, $reserved);
1970     $this->_prepend($header . $data);
1973 ###############################################################################
1975 # _store_selection($first_row, $first_col, $last_row, $last_col)
1977 # Write BIFF record SELECTION.
1979 function _store_selection($_) {
1981     $record   = 0x001D;                  # Record identifier
1982     $length   = 0x000F;                  # Number of bytes to follow
1984     $pnn      = $this->_active_pane;     # Pane position
1985     $rwAct    = $_[0];                   # Active row
1986     $colAct   = $_[1];                   # Active column
1987     $irefAct  = 0;                       # Active cell ref
1988     $cref     = 1;                       # Number of refs
1990     $rwFirst  = $_[0];                   # First row in reference
1991     $colFirst = $_[1];                   # First col in reference
1992     $rwLast   = $_[2] ? $_[2] : $rwFirst;       # Last  row in reference
1993     $colLast  = $_[3] ? $_[3] : $colFirst;      # Last  col in reference
1995     # Swap last row/col for first row/col as necessary
1996     if ($rwFirst > $rwLast) {
1997         list($rwFirst, $rwLast) = array($rwLast, $rwFirst);
1998     }
2000     if ($colFirst > $colLast) {
2001         list($colFirst, $colLast) = array($colLast, $colFirst);
2002     }
2004     $header   = pack("vv",           $record, $length);
2005     $data     = pack("CvvvvvvCC",    $pnn, $rwAct, $colAct,
2006                                      $irefAct, $cref,
2007                                      $rwFirst, $rwLast,
2008                                      $colFirst, $colLast);
2010     $this->_append($header . $data);
2013     /*
2014      * Write BIFF record EXTERNCOUNT to indicate the number of external
2015      * sheet references in a worksheet.
2016      *
2017      * Excel only stores references to external sheets that are used in
2018      * formulas. For simplicity we store references to all the sheets in
2019      * the workbook regardless of whether they are used or not. This reduces
2020      * the overall complexity and eliminates the need for a two way dialogue
2021      * between the formula parser the worksheet objects.
2022      */
2023     function _store_externcount($cxals) {
2024         // $cxals   Number of external references
2026         $record   = 0x0016;   // Record identifier
2027         $length   = 0x0002;   // Number of bytes to follow
2029         $header   = pack("vv", $record, $length);
2030         $data     = pack("v",  $cxals);
2032         $this->_prepend($header . $data);
2033     }
2035     /*
2036      * Writes the Excel BIFF EXTERNSHEET record. These references are used
2037      * by formulas. A formula references a sheet name via an index. Since we
2038      * store a reference to all of the external worksheets the EXTERNSHEET
2039      * index is the same as the worksheet index.
2040      */
2041     function _store_externsheet($sheetname) {
2042         $record    = 0x0017;         # Record identifier
2043         // $length   Number of bytes to follow
2045         // $cch      Length of sheet name
2046         // $rgch     Filename encoding
2048         // References to the current sheet are encoded differently to
2049         // references to external sheets.
2050         if ($this->_name == $sheetname) {
2051             $sheetname = '';
2052             $length    = 0x02;  // The following 2 bytes
2053             $cch       = 1;     // The following byte
2054             $rgch      = 0x02;  // Self reference
2055         } else {
2056             $length    = 0x02 + strlen($sheetname);
2057             $cch       = strlen($sheetname);
2058             $rgch      = 0x03;  // Reference to a sheet in the current
2059                                 // workbook
2060         }
2062         $header     = pack("vv",  $record, $length);
2063         $data       = pack("CC", $cch, $rgch);
2065         $this->_prepend($header . $data . $sheetname);
2066     }
2068 ###############################################################################
2070 # _store_panes()
2073 # Writes the Excel BIFF PANE record.
2074 # The panes can either be frozen or thawed (unfrozen).
2075 # Frozen panes are specified in terms of a integer number of rows and columns.
2076 # Thawed panes are specified in terms of Excel's units for rows and columns.
2078 function _store_panes() {
2080     $_=func_get_args();
2082     $record  = 0x0041;       # Record identifier
2083     $length  = 0x000A;       # Number of bytes to follow
2085     $y       = $_[0] || 0;   # Vertical split position
2086     $x       = $_[1] || 0;   # Horizontal split position
2087     if (isset($_[2])) {
2088         $rwTop   = $_[2];        # Top row visible
2089     }
2090     if (isset($_[3])) {
2091         $colLeft = $_[3];        # Leftmost column visible
2092     }
2093     if (isset($_[4])) {
2094         $pnnAct  = $_[4];        # Active pane
2095     }
2097     # Code specific to frozen or thawed panes.
2098     if ($this->_frozen) {
2099         # Set default values for $rwTop and $colLeft
2100         if (!isset($rwTop)) {
2101             $rwTop   = $y;
2102         }
2103         if (!isset($colLeft)) {
2104             $colLeft = $x;
2105         }
2106     } else {
2107         # Set default values for $rwTop and $colLeft
2108         if (!isset($rwTop)) {
2109             $rwTop   = 0;
2110         }
2111         if (!isset($colLeft)) {
2112             $colLeft = 0;
2113         }
2115         # Convert Excel's row and column units to the internal units.
2116         # The default row height is 12.75
2117         # The default column width is 8.43
2118         # The following slope and intersection values were interpolated.
2119         #
2120         $y = 20*$y      + 255;
2121         $x = 113.879*$x + 390;
2122     }
2124     # Determine which pane should be active. There is also the undocumented
2125     # option to override this should it be necessary: may be removed later.
2126     #
2127     if (!isset($pnnAct)) {
2128         # Bottom right
2129         if ($x != 0 && $y != 0) {
2130             $pnnAct = 0;
2131         }
2132         # Top right
2133         if ($x != 0 && $y == 0) {
2134             $pnnAct = 1;
2135         }
2136         # Bottom left
2137         if ($x == 0 && $y != 0) {
2138             $pnnAct = 2;
2139         }
2140         # Top left
2141         if ($x == 0 && $y == 0) {
2142             $pnnAct = 3;
2143         }
2144     }
2146     $this->_active_pane = $pnnAct; # Used in _store_selection
2148     $header     = pack("vv",    $record, $length);
2149     $data       = pack("vvvvv", $x, $y, $rwTop, $colLeft, $pnnAct);
2151     $this->_append($header . $data);
2154     /*
2155      * Store the page setup SETUP BIFF record.
2156      */
2157     function _store_setup() {
2158         $record       = 0x00A1;                // Record identifier
2159         $length       = 0x0022;                // Number of bytes to follow
2161         $iPaperSize   = $this->_paper_size;    // Paper size
2162         $iScale       = $this->_print_scale;   // Print scaling factor
2163         $iPageStart   = 0x01;                  // Starting page number
2164         $iFitWidth    = $this->_fit_width;     // Fit to number of pages wide
2165         $iFitHeight   = $this->_fit_height;    // Fit to number of pages high
2166         $grbit        = 0x00;                  // Option flags
2167         $iRes         = 0x0258;                // Print resolution
2168         $iVRes        = 0x0258;                // Vertical print resolution
2169         $numHdr       = $this->_margin_head;   // Header Margin
2170         $numFtr       = $this->_margin_foot;   // Footer Margin
2171         $iCopies      = 0x01;                  // Number of copies
2173         $fLeftToRight = 0x0;                   // Print over then down
2174         $fLandscape   = $this->_orientation;   // Page orientation
2175         $fNoPls       = 0x0;                   // Setup not read from printer
2176         $fNoColor     = 0x0;                   // Print black and white
2177         $fDraft       = 0x0;                   // Print draft quality
2178         $fNotes       = 0x0;                   // Print notes
2179         $fNoOrient    = 0x0;                   // Orientation not set
2180         $fUsePage     = 0x0;                   // Use custom starting page
2182         $grbit        = $fLeftToRight;
2183         $grbit       |= $fLandscape    << 1;
2184         $grbit       |= $fNoPls        << 2;
2185         $grbit       |= $fNoColor      << 3;
2186         $grbit       |= $fDraft        << 4;
2187         $grbit       |= $fNotes        << 5;
2188         $grbit       |= $fNoOrient     << 6;
2189         $grbit       |= $fUsePage      << 7;
2191         $numHdr = pack("d", $numHdr);
2192         $numFtr = pack("d", $numFtr);
2194         if ($this->_byte_order) {
2195             $numHdr = strrev($numHdr);
2196             $numFtr = strrev($numFtr);
2197         }
2199         $header = pack("vv",         $record, $length);
2200         $data1  = pack("vvvvvvvv",   $iPaperSize,
2201                                      $iScale,
2202                                      $iPageStart,
2203                                      $iFitWidth,
2204                                      $iFitHeight,
2205                                      $grbit,
2206                                      $iRes,
2207                                      $iVRes);
2208         $data2  = $numHdr . $numFtr;
2209         $data3  = pack("v", $iCopies);
2211         $this->_prepend($header . $data1 . $data2 . $data3);
2212     }
2214     /*
2215      * Store the header caption BIFF record.
2216      */
2217     function _store_header() {
2218         $record  = 0x0014;           // Record identifier
2220         $str     = $this->_header;   // header string
2221         $cch     = strlen($str);     // Length of header string
2222         $length  = 1 + $cch;         // Bytes to follow
2224         $header  = pack("vv",  $record, $length);
2225         $data    = pack("C",   $cch);
2227         $this->_append($header . $data . $str);
2228     }
2230     /*
2231      * Store the footer caption BIFF record.
2232      */
2233     function _store_footer() {
2234         $record  = 0x0015;           // Record identifier
2236         $str     = $this->_footer;   // Footer string
2237         $cch     = strlen($str);     // Length of footer string
2238         $length  = 1 + $cch;         // Bytes to follow
2240         $header  = pack("vv",  $record, $length);
2241         $data    = pack("C",   $cch);
2243         $this->_append($header . $data . $str);
2244     }
2246     /*
2247      * Store the horizontal centering HCENTER BIFF record.
2248      */
2249     function _store_hcenter() {
2250         $record   = 0x0083;   // Record identifier
2251         $length   = 0x0002;   // Bytes to follow
2253         $fHCenter = $this->_hcenter;   // Horizontal centering
2255         $header   = pack("vv",  $record, $length);
2256         $data     = pack("v",   $fHCenter);
2258         $this->_append($header . $data);
2259     }
2261      /*
2262       * Store the vertical centering VCENTER BIFF record.
2263       */
2264     function _store_vcenter() {
2265         $record   = 0x0084;   // Record identifier
2266         $length   = 0x0002;   // Bytes to follow
2268         $fVCenter = $this->_vcenter;   // Horizontal centering
2270         $header   = pack("vv",  $record, $length);
2271         $data     = pack("v",   $fVCenter);
2273         $this->_append($header . $data);
2274     }
2276     /*
2277      * Store the LEFTMARGIN BIFF record.
2278      */
2279     function _store_margin_left() {
2280         $record  = 0x0026;   // Record identifier
2281         $length  = 0x0008;   // Bytes to follow
2283         $margin  = $this->_margin_left;   // Margin in inches
2285         $header  = pack("vv",  $record, $length);
2286         $data    = pack("d",   $margin);
2288         if ($this->_byte_order) {
2289             $data = strrev($data);
2290         }
2292         $this->_append($header . $data);
2293     }
2295     /*
2296      * Store the RIGHTMARGIN BIFF record.
2297      */
2298     function _store_margin_right() {
2299         $record  = 0x0027;   // Record identifier
2300         $length  = 0x0008;   // Bytes to follow
2302         $margin  = $this->_margin_right;   // Margin in inches
2304         $header  = pack("vv",  $record, $length);
2305         $data    = pack("d",   $margin);
2307         if ($this->_byte_order) {
2308             $data = strrev($data);
2309         }
2311         $this->_append($header . $data);
2312     }
2314     /*
2315      * Store the TOPMARGIN BIFF record.
2316      */
2317     function _store_margin_top() {
2318         $record  = 0x0028;   // Record identifier
2319         $length  = 0x0008;   // Bytes to follow
2321         $margin  = $this->_margin_top;   // Margin in inches
2323         $header  = pack("vv",  $record, $length);
2324         $data    = pack("d",   $margin);
2326         if ($this->_byte_order) {
2327             $data = strrev($data);
2328         }
2330         $this->_append($header . $data);
2331     }
2333     /*
2334      * Store the BOTTOMMARGIN BIFF record.
2335      */
2336     function _store_margin_bottom() {
2337         $record  = 0x0029;   // Record identifier
2338         $length  = 0x0008;   // Bytes to follow
2340         $margin  = $this->_margin_bottom;   // Margin in inches
2342         $header  = pack("vv",  $record, $length);
2343         $data    = pack("d",   $margin);
2345         if ($this->_byte_order) {
2346             $data = strrev($data);
2347         }
2349         $this->_append($header . $data);
2350     }
2352 ###############################################################################
2354 # merge_cells($first_row, $first_col, $last_row, $last_col)
2356 # This is an Excel97/2000 method. It is required to perform more complicated
2357 # merging than the normal align merge in Format.pm
2359 function merge_cells() {
2361     $_=func_get_args();
2363     // Check for a cell reference in A1 notation and substitute row and column
2364     if (preg_match('/^\D/', $_[0])) {
2365         $_ = $this->_substitute_cellref($_);
2366     }
2368     $record  = 0x00E5;                   # Record identifier
2369     $length  = 0x000A;                   # Bytes to follow
2371     $cref     = 1;                       # Number of refs
2372     $rwFirst  = $_[0];                   # First row in reference
2373     $colFirst = $_[1];                   # First col in reference
2374     $rwLast   = $_[2] || $rwFirst;       # Last  row in reference
2375     $colLast  = $_[3] || $colFirst;      # Last  col in reference
2377     // Swap last row/col for first row/col as necessary
2378     if ($rwFirst > $rwLast) {
2379         list($rwFirst, $rwLast) = array($rwLast, $rwFirst);
2380     }
2382     if ($colFirst > $colLast) {
2383         list($colFirst, $colLast) = array($colLast, $colFirst);
2384     }
2386     $header   = pack("vv",       $record, $length);
2387     $data     = pack("vvvvv",    $cref,
2388                                  $rwFirst, $rwLast,
2389                                  $colFirst, $colLast);
2391     $this->_append($header . $data);
2394     /*
2395      * Write the PRINTHEADERS BIFF record.
2396      */
2397     function _store_print_headers() {
2398         $record      = 0x002a;   // Record identifier
2399         $length      = 0x0002;   // Bytes to follow
2401         $fPrintRwCol = $this->_print_headers;   // Boolean flag
2403         $header      = pack("vv",  $record, $length);
2404         $data        = pack("v",   $fPrintRwCol);
2406         $this->_prepend($header . $data);
2407     }
2409     /*
2410      * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction
2411      * with the GRIDSET record.
2412      */
2413     function _store_print_gridlines() {
2414         $record      = 0x002b;   // Record identifier
2415         $length      = 0x0002;   // Bytes to follow
2417         $fPrintGrid  = $this->_print_gridlines;   // Boolean flag
2419         $header      = pack("vv",  $record, $length);
2420         $data        = pack("v",   $fPrintGrid);
2422         $this->_prepend($header . $data);
2423     }
2425     /*
2426      * Write the GRIDSET BIFF record. Must be used in conjunction with the
2427      * PRINTGRIDLINES record.
2428      */
2429     function _store_gridset() {
2430         $record      = 0x0082;   // Record identifier
2431         $length      = 0x0002;   // Bytes to follow
2433         $fGridSet    = !$this->_print_gridlines;   // Boolean flag
2435         $header      = pack("vv",  $record, $length);
2436         $data        = pack("v",   $fGridSet);
2438         $this->_prepend($header . $data);
2439     }
2441     /*
2442      * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in
2443      * conjunction with the SETUP record.
2444      */
2445     function _store_wsbool() {
2446         $record      = 0x0081;   # Record identifier
2447         $length      = 0x0002;   # Bytes to follow
2449         // $grbit   Option flags
2451         // The only option that is of interest is the flag for fit to page.
2452         // So we set all the options in one go.
2453         if ($this->_fit_page) {
2454             $grbit = 0x05c1;
2455         } else {
2456             $grbit = 0x04c1;
2457         }
2459         $header      = pack("vv",  $record, $length);
2460         $data        = pack("v",   $grbit);
2462         $this->_prepend($header . $data);
2463     }
2465     /*
2466      * Write the HORIZONTALPAGEBREAKS BIFF record.
2467      */
2468     function _store_hbreak() {
2469         // Return if the user hasn't specified pagebreaks
2470         if(sizeof($this->_hbreaks)==0) {
2471             return;
2472         }
2474         # Sort and filter array of page breaks
2475         $breaks  = $this->_sort_pagebreaks($this->_hbreaks);
2477         $record  = 0x001b;             // Record identifier
2478         $cbrk    = sizeof($breaks);    // Number of page breaks
2479         $length  = ($cbrk + 1) * 2;    // Bytes to follow
2481         $header  = pack("vv",  $record, $length);
2482         $data    = pack("v",   $cbrk);
2484         // Append each page break
2485         foreach ($breaks as $break) {
2486             $data .= pack("v", $break);
2487         }
2489         $this->_prepend($header . $data);
2490     }
2492     /*
2493      * Write the VERTICALPAGEBREAKS BIFF record.
2494      */
2495     function _store_vbreak() {
2496         // Return if the user hasn't specified pagebreaks
2497         if(sizeof($this->_vbreaks)==0) {
2498             return;
2499         }
2501         // Sort and filter array of page breaks
2502         $breaks  = $this->_sort_pagebreaks($this->_vbreaks);
2504         $record  = 0x001a;            // Record identifier
2505         $cbrk    = sizeof($breaks);   // Number of page breaks
2506         $length  = ($cbrk + 1) * 2;   // Bytes to follow
2508         $header  = pack("vv",  $record, $length);
2509         $data    = pack("v",   $cbrk);
2511         // Append each page break
2512         foreach ($breaks as $break) {
2513             $data .= pack("v", $break);
2514         }
2516         $this->_prepend($header . $data);
2517     }
2519     /*
2520      * Set the Biff PROTECT record to indicate that the worksheet is
2521      * protected.
2522      */
2523     function _store_protect() {
2524         // Exit unless sheet protection has been specified
2525         if (!$this->_protect) {
2526             return;
2527         }
2529         $record      = 0x0012;            // Record identifier
2530         $length      = 0x0002;            // Bytes to follow
2532         $fLock       = $this->_protect;   // Worksheet is protected
2534         $header      = pack("vv", $record, $length);
2535         $data        = pack("v",  $fLock);
2537         $this->_prepend($header . $data);
2538     }
2540     /*
2541      * Write the worksheet PASSWORD record.
2542      */
2543     function _store_password() {
2544         // Exit unless sheet protection and password have been specified
2545         if (!$this->_protect || !$this->_password) {
2546             return;
2547         }
2549         $record      = 0x0013;             // Record identifier
2550         $length      = 0x0002;             // Bytes to follow
2552         $wPassword   = $this->_password;   // Encoded password
2554         $header      = pack("vv", $record, $length);
2555         $data        = pack("v",  $wPassword);
2557         $this->_prepend($header . $data);
2558     }
2560 ###############################################################################
2562 # insert_bitmap($row, $col, $filename, $x, $y, $scale_x, $scale_y)
2564 # Insert a 24bit bitmap image in a worksheet. The main record required is
2565 # IMDATA but it must be proceeded by a OBJ record to define its position.
2567 function insert_bitmap() {
2569     $_=func_get_args();
2571     # Check for a cell reference in A1 notation and substitute row and column
2572     if (preg_match('/^\D/', $_[0])) {
2573         $_ = $this->_substitute_cellref($_);
2574     }
2576     $row         = $_[0];
2577     $col         = $_[1];
2578     $bitmap      = $_[2];
2579     $x           = $_[3] ? $_[3] : 0;
2580     $y           = $_[4] ? $_[4] : 0;
2581     $scale_x     = $_[5] ? $_[5] : 1;
2582     $scale_y     = $_[6] ? $_[6] : 1;
2584     list($width, $height, $size, $data) = $this->_process_bitmap($bitmap);
2586     # Scale the frame of the image.
2587     $width  *= $scale_x;
2588     $height *= $scale_y;
2590     # Calculate the vertices of the image and write the OBJ record
2591     $this->_position_image($col, $row, $x, $y, $width, $height);
2593     # Write the IMDATA record to store the bitmap data
2594     $record      = 0x007f;
2595     $length      = 8 + $size;
2596     $cf          = 0x09;
2597     $env         = 0x01;
2598     $lcb         = $size;
2600     $header      = pack("vvvvV", $record, $length, $cf, $env, $lcb);
2602     $this->_append($header . $data);
2605     /*
2606      * Calculate the vertices that define the position of the image as
2607      * required by the OBJ record.
2608      *
2609      *        +------------+------------+
2610      *        |     A      |      B     |
2611      *  +-----+------------+------------+
2612      *  |     |(x1,y1)     |            |
2613      *  |  1  |(A1)._______|______      |
2614      *  |     |    |              |     |
2615      *  |     |    |              |     |
2616      *  +-----+----|    BITMAP    |-----+
2617      *  |     |    |              |     |
2618      *  |  2  |    |______________.     |
2619      *  |     |            |        (B2)|
2620      *  |     |            |     (x2,y2)|
2621      *  +---- +------------+------------+
2622      *
2623      * Example of a bitmap that covers some of the area from cell A1 to
2624      * cell B2.
2625      *
2626      * Based on the width and height of the bitmap we need to calculate 8
2627      *vars:
2628      *    $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
2629      * The width and height of the cells are also variable and have to be
2630      * taken into account.
2631      * The values of $col_start and $row_start are passed in from the calling
2632      * function. The values of $col_end and $row_end are calculated by
2633      * subtracting the width and height of the bitmap from the width and
2634      * height of the underlying cells.
2635      * The vertices are expressed as a percentage of the underlying cell
2636      * width as follows (rhs values are in pixels):
2637      *
2638      *      x1 = X / W *1024
2639      *      y1 = Y / H *256
2640      *      x2 = (X-1) / W *1024
2641      *      y2 = (Y-1) / H *256
2642      *
2643      *      Where:  X is distance from the left side of the underlying cell
2644      *              Y is distance from the top of the underlying cell
2645      *              W is the width of the cell
2646      *              H is the height of the cell
2647      *
2648      * Note: the SDK incorrectly states that the height should be expressed
2649      * as a percentage of 1024.
2650      */
2651     function _position_image($col_start, $row_start, $x1, $y1,
2652                              $width, $height) {
2653         // $col_start   Col containing upper left corner of object
2654         // $x1          Distance to left side of object
2656         // $row_start   Row containing top left corner of object
2657         // $y1          Distance to top of object
2659         // $col_end     Col containing lower right corner of object
2660         // $x2          Distance to right side of object
2662         // $row_end     Row containing bottom right corner of object
2663         // $y2          Distance to bottom of object
2665         // $width       Width of image frame
2666         // $height      Height of image frame
2668         // Initialise end cell to the same as the start cell
2669         $col_end = $col_start;
2670         $row_end = $row_start;
2672         // Zero the specified offset if greater than the cell dimensions
2673         if ($x1 >= $this->_size_col($col_start)) {
2674             $x1 = 0;
2675         }
2676         if ($y1 >= $this->_size_row($row_start)) {
2677             $y1 = 0;
2678         }
2680         $width  = $width  + $x1 -1;
2681         $height = $height + $y1 -1;
2683         // Subtract the underlying cell widths to find the end cell of the
2684         // image
2685         while ($width >= $this->_size_col($col_end)) {
2686             $width -= $this->_size_col($col_end);
2687             $col_end++;
2688         }
2690         // Subtract the underlying cell heights to find the end cell of the
2691         // image
2692         while ($height >= $this->_size_row($row_end)) {
2693             $height -= $this->_size_row($row_end);
2694             $row_end++;
2695         }
2697         // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a
2698         // cell with zero height or width.
2699         if ($this->_size_col($col_start) == 0) { return; }
2700         if ($this->_size_col($col_end)   == 0) { return; }
2701         if ($this->_size_row($row_start) == 0) { return; }
2702         if ($this->_size_row($row_end)   == 0) { return; }
2704         // Convert the pixel values to the percentage value expected by Excel
2705         $x1 = $x1     / $this->_size_col($col_start) * 1024;
2706         $y1 = $y1     / $this->_size_row($row_start) *  256;
2707         $x2 = $width  / $this->_size_col($col_end)   * 1024;
2708         $y2 = $height / $this->_size_row($row_end)   *  256;
2710         $this->_store_obj_picture($col_start, $x1, $row_start, $y1,
2711                                   $col_end, $x2, $row_end, $y2);
2712     }
2714     /*
2715      * Convert the width of a cell from user's units to pixels. By
2716      * interpolation the relationship is: y = 7x +5. If the width
2717      * hasn't been set by the user we use the default value. If the
2718      * col is hidden we use a value of zero.
2719      */
2720     function _size_col($col) {
2721         // Look up the cell value to see if it has been changed
2722         if (isset($this->_col_sizes[$col])) {
2723             if ($this->_col_sizes[$col] == 0) {
2724                 return 0;
2725             } else {
2726                 return floor(7 * $this->_col_sizes[$col] + 5);
2727             }
2728         } else {
2729             return 64;
2730         }
2731     }
2733     /*
2734      * Convert the height of a cell from user's units to pixels. By
2735      * interpolation # the relationship is: y = 4/3x. If the height
2736      * hasn't been set by the user we use the default value. If the
2737      * row is hidden we use a value of zero. (Not possible to hide row
2738      * yet).
2739      */
2740     function _size_row($row) {
2741         // Look up the cell value to see if it has been changed
2742         if (isset($this->_row_sizes[$row])) {
2743             if ($this->_row_sizes[$row] == 0) {
2744                 return 0;
2745             } else {
2746                 return floor(4/3 * $this->_row_sizes[$row]);
2747             }
2748         } else {
2749             return 17;
2750         }
2751     }
2753     /*
2754      * Store the OBJ record that precedes an IMDATA record. This could
2755      * be generalized to support other Excel objects.
2756      */
2757     function _store_obj_picture($col_start, $x1, $row_start, $y1,
2758                                 $col_end, $x2, $row_end, $y2) {
2759         $record      = 0x005d;       // Record identifier
2760         $length      = 0x003c;       // Bytes to follow
2762         $cObj        = 0x0001;       // Count of objects in file (set to 1)
2763         $OT          = 0x0008;       // Object type. 8 = Picture
2764         $id          = 0x0001;       // Object ID
2765         $grbit       = 0x0614;       // Option flags
2767         $colL        = $col_start;   // Col containing upper left corner of
2768                                      // object
2769         $dxL         = $x1;          // Distance from left side of cell
2771         $rwT         = $row_start;   // Row containing top left corner of
2772                                      // object
2773         $dyT         = $y1;          // Distance from top of cell
2775         $colR        = $col_end;     // Col containing lower right corner of 
2776                                      // object
2777         $dxR         = $x2;          // Distance from right of cell
2779         $rwB         = $row_end;     // Row containing bottom right corner of
2780                                      // object
2781         $dyB         = $y2;          // Distance from bottom of cell
2783         $cbMacro     = 0x0000;       // Length of FMLA structure
2784         $Reserved1   = 0x0000;       // Reserved
2785         $Reserved2   = 0x0000;       // Reserved
2787         $icvBack     = 0x09;         // Background colour
2788         $icvFore     = 0x09;         // Foreground colour
2789         $fls         = 0x00;         // Fill pattern
2790         $fAuto       = 0x00;         // Automatic fill
2791         $icv         = 0x08;         // Line colour
2792         $lns         = 0xff;         // Line style
2793         $lnw         = 0x01;         // Line weight
2794         $fAutoB      = 0x00;         // Automatic border
2795         $frs         = 0x0000;       // Frame style
2796         $cf          = 0x0009;       // Image format, 9 = bitmap
2797         $Reserved3   = 0x0000;       // Reserved
2798         $cbPictFmla  = 0x0000;       // Length of FMLA structure
2799         $Reserved4   = 0x0000;       // Reserved
2800         $grbit2      = 0x0001;       // Option flags
2801         $Reserved5   = 0x0000;       // Reserved
2803         $header      = pack("vv", $record, $length);
2804         $data        = pack("V",  $cObj);
2805         $data       .= pack("v",  $OT);
2806         $data       .= pack("v",  $id);
2807         $data       .= pack("v",  $grbit);
2808         $data       .= pack("v",  $colL);
2809         $data       .= pack("v",  $dxL);
2810         $data       .= pack("v",  $rwT);
2811         $data       .= pack("v",  $dyT);
2812         $data       .= pack("v",  $colR);
2813         $data       .= pack("v",  $dxR);
2814         $data       .= pack("v",  $rwB);
2815         $data       .= pack("v",  $dyB);
2816         $data       .= pack("v",  $cbMacro);
2817         $data       .= pack("V",  $Reserved1);
2818         $data       .= pack("v",  $Reserved2);
2819         $data       .= pack("C",  $icvBack);
2820         $data       .= pack("C",  $icvFore);
2821         $data       .= pack("C",  $fls);
2822         $data       .= pack("C",  $fAuto);
2823         $data       .= pack("C",  $icv);
2824         $data       .= pack("C",  $lns);
2825         $data       .= pack("C",  $lnw);
2826         $data       .= pack("C",  $fAutoB);
2827         $data       .= pack("v",  $frs);
2828         $data       .= pack("V",  $cf);
2829         $data       .= pack("v",  $Reserved3);
2830         $data       .= pack("v",  $cbPictFmla);
2831         $data       .= pack("v",  $Reserved4);
2832         $data       .= pack("v",  $grbit2);
2833         $data       .= pack("V",  $Reserved5);
2835         $this->_append($header . $data);
2836     }
2838     /*
2839      * Convert a 24 bit bitmap into the modified internal format used by
2840      * Windows. This is described in BITMAPCOREHEADER and BITMAPCOREINFO
2841      * structures in the MSDN library.
2842      */
2843     function _process_bitmap($bitmap) {
2844         // Open file and binmode the data in case the platform needs it.
2845         $bmp=fopen($bitmap, "rb");
2846         if (!$bmp) {
2847             trigger_error("Could not open file '$bitmap'.", E_USER_ERROR);
2848         }
2850         $data=fread($bmp, filesize($bitmap));
2852         // Check that the file is big enough to be a bitmap.
2853         if (strlen($data) <= 0x36) {
2854             trigger_error("$bitmap doesn't contain enough data.",
2855                           E_USER_ERROR);
2856         }
2858         // The first 2 bytes are used to identify the bitmap.
2859         if (substr($data, 0, 2) != "BM") {
2860             trigger_error("$bitmap doesn't appear to to be a ".
2861                           "valid bitmap image.", E_USER_ERROR);
2862         }
2864         // Remove bitmap data: ID.
2865         $data = substr($data, 2);
2867         // Read and remove the bitmap size. This is more reliable than reading
2868         // the data size at offset 0x22.
2869         $array = unpack("Vsize", $data);
2870         $data = substr($data, 4);
2871         $size   =  $array["size"];
2872         $size  -=  0x36;   # Subtract size of bitmap header.
2873         $size  +=  0x0C;   # Add size of BIFF header.
2875         // Remove bitmap data: reserved, offset, header length.
2876         $data = substr($data, 12);
2878         // Read and remove the bitmap width and height. Verify the sizes.
2879         $array = unpack("Vwidth/Vheight", $data);
2880         $data = substr($data, 8);
2881         $width = $array["width"];
2882         $height = $array["height"];
2884         if ($width > 0xFFFF) {
2885             trigger_error("$bitmap: largest image width supported is 64k.",
2886                           E_USER_ERROR);
2887         }
2889         if ($height > 0xFFFF) {
2890             trigger_error("$bitmap: largest image height supported is 64k.",
2891                           E_USER_ERROR);
2892         }
2894         // Read and remove the bitmap planes and bpp data. Verify them.
2895         $array = unpack("vplanes/vbitcount", $data);
2896         $data = substr($data, 4);
2897         $planes = $array["planes"];
2898         $bitcount = $array["bitcount"];
2900         if ($bitcount != 24) {
2901             trigger_error("$bitmap isn't a 24bit true color bitmap.",
2902                           E_USER_ERROR);
2903         }
2905         if ($planes != 1) {
2906             trigger_error("$bitmap: only 1 plane supported in bitmap image.",
2907                           E_USER_ERROR);
2908         }
2910         // Read and remove the bitmap compression. Verify compression.
2911         $array = unpack("Vcompression", $data);
2912         $data = substr($data, 4);
2913         $compression = $array["compression"];
2915         if ($compression != 0) {
2916             trigger_error("$bitmap: compression not supported in bitmap image.",
2917                           E_USER_ERROR);
2918         }
2920         // Remove bitmap data: data size, hres, vres, colours, imp. colours.
2921         $data = substr($data, 20);
2923         // Add the BITMAPCOREHEADER data
2924         $header = pack("Vvvvv", 0x000c, $width, $height, 0x01, 0x18);
2925         $data = $header . $data;
2927         return array($width, $height, $size, $data);
2928     }
2930     /*
2931      * Store the window zoom factor. This should be a reduced fraction but for
2932      * simplicity we will store all fractions with a numerator of 100.
2933      */
2934     function _store_zoom() {
2935         // If scale is 100% we don't need to write a record
2936         if ($this->_zoom == 100) {
2937             return;
2938         }
2940         $record = 0x00A0; // Record identifier
2941         $length = 0x0004; // Bytes to follow
2943         $header = pack("vv", $record, $length);
2944         $data   = pack("vv", $this->_zoom, 100);
2946         $this->_append($header . $data);
2947     }
2951 ?>