Subversion Repositories eFlore/Applications.cel

Rev

Rev 1603 | Rev 1605 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1603 Rev 1604
Line 3... Line 3...
3
*  Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
3
*  Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
4
*
4
*
5
*  The majority of this is _NOT_ my code.  I simply ported it from the
5
*  The majority of this is _NOT_ my code.  I simply ported it from the
6
*  PERL Spreadsheet::WriteExcel module.
6
*  PERL Spreadsheet::WriteExcel module.
7
*
7
*
8
*  The author of the Spreadsheet::WriteExcel module is John McNamara 
8
*  The author of the Spreadsheet::WriteExcel module is John McNamara
9
*  <jmcnamara@cpan.org>
9
*  <jmcnamara@cpan.org>
10
*
10
*
11
*  I _DO_ maintain this code, and John McNamara has nothing to do with the
11
*  I _DO_ maintain this code, and John McNamara has nothing to do with the
12
*  porting of this code to PHP.  Any questions directly related to this
12
*  porting of this code to PHP.  Any questions directly related to this
13
*  class library should be directed to me.
13
*  class library should be directed to me.
Line 30... Line 30...
30
*    You should have received a copy of the GNU Lesser General Public
30
*    You should have received a copy of the GNU Lesser General Public
31
*    License along with this library; if not, write to the Free Software
31
*    License along with this library; if not, write to the Free Software
32
*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
32
*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
33
*/
33
*/
Line 34... Line 34...
34
 
34
 
35
require_once('Format.php');
35
require_once 'Spreadsheet/Excel/Writer/Format.php';
-
 
36
require_once 'Spreadsheet/Excel/Writer/BIFFwriter.php';
36
require_once('OLEwriter.php');
37
require_once 'Spreadsheet/Excel/Writer/Worksheet.php';
37
require_once('BIFFwriter.php');
38
require_once 'Spreadsheet/Excel/Writer/Parser.php';
38
require_once('Worksheet.php');
39
require_once 'OLE/PPS/Root.php';
Line 39... Line 40...
39
require_once('Parser.php');
40
require_once 'OLE/PPS/File.php';
40
 
41
 
41
/**
42
/**
42
* Class for generating Excel Spreadsheets
43
* Class for generating Excel Spreadsheets
Line 59... Line 60...
59
    * @var object Parser
60
    * @var object Parser
60
    */
61
    */
61
    var $_parser;
62
    var $_parser;
Line 62... Line 63...
62
 
63
 
63
    /**
64
    /**
64
    * Flag for 1904 date system
65
    * Flag for 1904 date system (0 => base date is 1900, 1 => base date is 1904)
65
    * @var integer
66
    * @var integer
66
    */
67
    */
Line 67... Line 68...
67
    var $_1904;
68
    var $_1904;
Line 145... Line 146...
145
    * @var object Format
146
    * @var object Format
146
    */
147
    */
147
    var $_url_format;
148
    var $_url_format;
Line 148... Line 149...
148
 
149
 
-
 
150
    /**
-
 
151
    * The codepage indicates the text encoding used for strings
-
 
152
    * @var integer
-
 
153
    */
-
 
154
    var $_codepage;
-
 
155
 
-
 
156
    /**
-
 
157
    * The country code used for localization
-
 
158
    * @var integer
-
 
159
    */
-
 
160
    var $_country_code;
-
 
161
 
-
 
162
    /**
-
 
163
    * number of bytes for sizeinfo of strings
-
 
164
    * @var integer
-
 
165
    */
-
 
166
    var $_string_sizeinfo_size;
-
 
167
 
149
    /**
168
    /**
150
    * Class constructor
169
    * Class constructor
151
    *
170
    *
152
    * @param string filename for storing the workbook. "-" for writing to stdout.
171
    * @param string filename for storing the workbook. "-" for writing to stdout.
153
    * @access public
172
    * @access public
154
    */
173
    */
155
    function Spreadsheet_Excel_Writer_Workbook($filename)
174
    function Spreadsheet_Excel_Writer_Workbook($filename)
156
    {
175
    {
157
        // It needs to call its parent's constructor explicitly
176
        // It needs to call its parent's constructor explicitly
158
        $this->Spreadsheet_Excel_Writer_BIFFwriter();
177
        $this->Spreadsheet_Excel_Writer_BIFFwriter();
159
    
178
 
160
        $this->_filename         = $filename;
179
        $this->_filename         = $filename;
161
        $this->_parser           = new Spreadsheet_Excel_Writer_Parser($this->_byte_order);
180
        $this->_parser           = new Spreadsheet_Excel_Writer_Parser($this->_byte_order, $this->_BIFF_version);
162
        $this->_1904             = 0;
181
        $this->_1904             = 0;
163
        $this->_activesheet      = 0;
182
        $this->_activesheet      = 0;
164
        $this->_firstsheet       = 0;
183
        $this->_firstsheet       = 0;
165
        $this->_selected         = 0;
184
        $this->_selected         = 0;
166
        $this->_xf_index         = 16; // 15 style XF's and 1 cell XF.
185
        $this->_xf_index         = 16; // 15 style XF's and 1 cell XF.
167
        $this->_fileclosed       = 0;
186
        $this->_fileclosed       = 0;
168
        $this->_biffsize         = 0;
187
        $this->_biffsize         = 0;
169
        $this->_sheetname        = "Sheet";
188
        $this->_sheetname        = 'Sheet';
170
        $this->_tmp_format       = new Spreadsheet_Excel_Writer_Format();
189
        $this->_tmp_format       = new Spreadsheet_Excel_Writer_Format($this->_BIFF_version);
171
        $this->_worksheets       = array();
190
        $this->_worksheets       = array();
172
        $this->_sheetnames       = array();
191
        $this->_sheetnames       = array();
173
        $this->_formats          = array();
192
        $this->_formats          = array();
-
 
193
        $this->_palette          = array();
-
 
194
        $this->_codepage         = 0x04E4; // FIXME: should change for BIFF8
-
 
195
        $this->_country_code     = -1;
174
        $this->_palette          = array();
196
        $this->_string_sizeinfo  = 3;
175
    
197
 
176
        // Add the default format for hyperlinks
198
        // Add the default format for hyperlinks
177
        $this->_url_format =& $this->addFormat(array('color' => 'blue', 'underline' => 1));
-
 
-
 
199
        $this->_url_format =& $this->addFormat(array('color' => 'blue', 'underline' => 1));
-
 
200
        $this->_str_total       = 0;
-
 
201
        $this->_str_unique      = 0;
178
    
202
        $this->_str_table       = array();
179
        $this->_setPaletteXl97();
203
        $this->_setPaletteXl97();
180
    }
204
    }
181
    
205
 
182
    /**
206
    /**
183
    * Calls finalization methods.
207
    * Calls finalization methods.
184
    * This method should always be the last one to be called on every workbook
208
    * This method should always be the last one to be called on every workbook
185
    *
209
    *
-
 
210
    * @access public
186
    * @access public
211
    * @return mixed true on success. PEAR_Error on failure
187
    */
212
    */
188
    function close()
213
    function close()
189
    {
214
    {
190
        if ($this->_fileclosed) { // Prevent close() from being called twice.
215
        if ($this->_fileclosed) { // Prevent close() from being called twice.
-
 
216
            return true;
-
 
217
        }
-
 
218
        $res = $this->_storeWorkbook();
-
 
219
        if ($this->isError($res)) {
191
            return;
220
            return $this->raiseError($res->getMessage());
192
        }
-
 
193
        $this->_storeWorkbook();
221
        }
-
 
222
        $this->_fileclosed = 1;
194
        $this->_fileclosed = 1;
223
        return true;
195
    }
-
 
196
    
224
    }
197
    
225
 
198
    /**
226
    /**
199
    * An accessor for the _worksheets[] array
227
    * An accessor for the _worksheets[] array
200
    * Returns an array of the worksheet objects in a workbook
228
    * Returns an array of the worksheet objects in a workbook
201
    * It actually calls to worksheets()
229
    * It actually calls to worksheets()
Line 206... Line 234...
206
    */
234
    */
207
    function sheets()
235
    function sheets()
208
    {
236
    {
209
        return $this->worksheets();
237
        return $this->worksheets();
210
    }
238
    }
211
    
239
 
212
    /**
240
    /**
213
    * An accessor for the _worksheets[] array.
241
    * An accessor for the _worksheets[] array.
214
    * Returns an array of the worksheet objects in a workbook
242
    * Returns an array of the worksheet objects in a workbook
215
    *
243
    *
216
    * @access public
244
    * @access public
217
    * @return array
245
    * @return array
218
    */
246
    */
219
    function worksheets()
247
    function worksheets()
220
    {
248
    {
221
        return($this->_worksheets);
249
        return $this->_worksheets;
-
 
250
    }
-
 
251
 
-
 
252
    /**
-
 
253
    * Sets the BIFF version.
-
 
254
    * This method exists just to access experimental functionality
-
 
255
    * from BIFF8. It will be deprecated !
-
 
256
    * Only possible value is 8 (Excel 97/2000).
-
 
257
    * For any other value it fails silently.
-
 
258
    *
-
 
259
    * @access public
-
 
260
    * @param integer $version The BIFF version
-
 
261
    */
-
 
262
    function setVersion($version)
-
 
263
    {
-
 
264
        if ($version == 8) { // only accept version 8
-
 
265
            $version = 0x0600;
-
 
266
            $this->_BIFF_version = $version;
-
 
267
            // change BIFFwriter limit for CONTINUE records
-
 
268
            $this->_limit = 8228;
-
 
269
            $this->_tmp_format->_BIFF_version = $version;
-
 
270
            $this->_url_format->_BIFF_version = $version;
-
 
271
            $this->_parser->_BIFF_version = $version;
-
 
272
            $this->_codepage = 0x04B0;
-
 
273
 
-
 
274
            $total_worksheets = count($this->_worksheets);
-
 
275
            // change version for all worksheets too
-
 
276
            for ($i = 0; $i < $total_worksheets; $i++) {
-
 
277
                $this->_worksheets[$i]->_BIFF_version = $version;
-
 
278
            }
-
 
279
 
-
 
280
            $total_formats = count($this->_formats);
-
 
281
            // change version for all formats too
-
 
282
            for ($i = 0; $i < $total_formats; $i++) {
-
 
283
                $this->_formats[$i]->_BIFF_version = $version;
-
 
284
            }
-
 
285
        }
222
    }
286
    }
223
    
287
 
-
 
288
    /**
-
 
289
    * Set the country identifier for the workbook
-
 
290
    *
-
 
291
    * @access public
-
 
292
    * @param integer $code Is the international calling country code for the
-
 
293
    *                      chosen country.
-
 
294
    */
-
 
295
    function setCountry($code)
-
 
296
    {
-
 
297
        $this->_country_code = $code;
-
 
298
    }
-
 
299
 
224
    /**
300
    /**
225
    * Add a new worksheet to the Excel workbook.
301
    * Add a new worksheet to the Excel workbook.
226
    * If no name is given the name of the worksheet will be Sheeti$i, with
302
    * If no name is given the name of the worksheet will be Sheeti$i, with
227
    * $i in [1..].
303
    * $i in [1..].
228
    *
304
    *
229
    * @access public
305
    * @access public
230
    * @param string $name the optional name of the worksheet
306
    * @param string $name the optional name of the worksheet
231
    * @return &Spreadsheet_Excel_Writer_Worksheet reference to a worksheet object
307
    * @return mixed reference to a worksheet object on success, PEAR_Error
-
 
308
    *               on failure
232
    */
309
    */
233
    function &addWorksheet($name = '')
310
    function &addWorksheet($name = '')
234
    {
311
    {
235
        $index     = count($this->_worksheets);
312
        $index     = count($this->_worksheets);
236
        $sheetname = $this->_sheetname;
313
        $sheetname = $this->_sheetname;
237
    
314
 
238
        if($name == '') {
315
        if ($name == '') {
239
            $name = $sheetname.($index+1); 
316
            $name = $sheetname.($index+1);
240
        }
-
 
241
    
-
 
242
        // Check that sheetname is <= 31 chars (Excel limit).
-
 
243
        if(strlen($name) > 31) {
-
 
244
            $this->raiseError("Sheetname $name must be <= 31 chars");
-
 
245
        }
317
        }
246
    
318
 
247
        // Check that the worksheet name doesn't already exist: a fatal Excel error.
319
        // Check that sheetname is <= 31 chars (Excel limit before BIFF8).
248
        for($i=0; $i < count($this->_worksheets); $i++)
320
        if ($this->_BIFF_version != 0x0600)
249
        {
321
        {
-
 
322
            if (strlen($name) > 31) {
-
 
323
                return $this->raiseError("Sheetname $name must be <= 31 chars");
-
 
324
            }
-
 
325
        } else {
-
 
326
            if(function_exists('iconv')) {
-
 
327
                $name = iconv('UTF-8','UTF-16LE',$name);
-
 
328
            }
-
 
329
        }
-
 
330
 
-
 
331
        // Check that the worksheet name doesn't already exist: a fatal Excel error.
-
 
332
        $total_worksheets = count($this->_worksheets);
-
 
333
        for ($i = 0; $i < $total_worksheets; $i++) {
250
            if($name == $this->_worksheets[$i]->getName()) {
334
            if ($this->_worksheets[$i]->getName() == $name) {
251
                $this->raiseError("Worksheet '$name' already exists");
335
                return $this->raiseError("Worksheet '$name' already exists");
252
            }
336
            }
253
        }
337
        }
254
    
338
 
255
        $worksheet = new Spreadsheet_Excel_Writer_Worksheet($name,$index,$this->_activesheet,
339
        $worksheet = new Spreadsheet_Excel_Writer_Worksheet($this->_BIFF_version,
-
 
340
                                   $name, $index,
-
 
341
                                   $this->_activesheet, $this->_firstsheet,
-
 
342
                                   $this->_str_total, $this->_str_unique,
256
                                   $this->_firstsheet,$this->_url_format,
343
                                   $this->_str_table, $this->_url_format,
257
                                   $this->_parser);
344
                                   $this->_parser, $this->_tmp_dir);
Line 258... Line 345...
258
 
345
 
259
        $this->_worksheets[$index] = &$worksheet;    // Store ref for iterator
346
        $this->_worksheets[$index] = &$worksheet;    // Store ref for iterator
260
        $this->_sheetnames[$index] = $name;          // Store EXTERNSHEET names
347
        $this->_sheetnames[$index] = $name;          // Store EXTERNSHEET names
261
        $this->_parser->setExtSheet($name, $index);  // Register worksheet name with parser
348
        $this->_parser->setExtSheet($name, $index);  // Register worksheet name with parser
262
        return($worksheet);
349
        return $worksheet;
263
    }
350
    }
264
    
351
 
265
    /**
352
    /**
266
    * Add a new format to the Excel workbook.
353
    * Add a new format to the Excel workbook.
267
    * Also, pass any properties to the Format constructor.
354
    * Also, pass any properties to the Format constructor.
268
    *
355
    *
269
    * @access public
356
    * @access public
270
    * @param array $properties array with properties for initializing the format.
357
    * @param array $properties array with properties for initializing the format.
271
    * @return &Spreadsheet_Excel_Writer_Format reference to an Excel Format
358
    * @return &Spreadsheet_Excel_Writer_Format reference to an Excel Format
272
    */
359
    */
273
    function &addFormat($properties = array())
360
    function &addFormat($properties = array())
274
    {
361
    {
275
        $format = new Spreadsheet_Excel_Writer_Format($this->_xf_index,$properties);
362
        $format = new Spreadsheet_Excel_Writer_Format($this->_BIFF_version, $this->_xf_index, $properties);
276
        $this->_xf_index += 1;
363
        $this->_xf_index += 1;
277
        $this->_formats[] = &$format;
364
        $this->_formats[] = &$format;
278
        return($format);
365
        return $format;
279
    }
366
    }
-
 
367
 
-
 
368
    /**
-
 
369
     * Create new validator.
-
 
370
     *
-
 
371
     * @access public
-
 
372
     * @return &Spreadsheet_Excel_Writer_Validator reference to a Validator
-
 
373
     */
-
 
374
    function &addValidator()
-
 
375
    {
-
 
376
        include_once 'Spreadsheet/Excel/Writer/Validator.php';
-
 
377
        /* FIXME: check for successful inclusion*/
-
 
378
        $valid = new Spreadsheet_Excel_Writer_Validator($this->_parser);
-
 
379
        return $valid;
-
 
380
    }
280
    
381
 
281
    /**
382
    /**
282
    * Change the RGB components of the elements in the colour palette.
383
    * Change the RGB components of the elements in the colour palette.
283
    *
384
    *
284
    * @access public
385
    * @access public
285
    * @param integer $index colour index
386
    * @param integer $index colour index
286
    * @param integer $red   red RGB value [0-255]
387
    * @param integer $red   red RGB value [0-255]
287
    * @param integer $green green RGB value [0-255]
388
    * @param integer $green green RGB value [0-255]
288
    * @param integer $blue  blue RGB value [0-255]
389
    * @param integer $blue  blue RGB value [0-255]
289
    * @return integer The palette index for the custom color
390
    * @return integer The palette index for the custom color
290
    */
391
    */
291
    function setCustomColor($index,$red,$green,$blue)
392
    function setCustomColor($index, $red, $green, $blue)
292
    {
393
    {
293
        // Match a HTML #xxyyzz style parameter
394
        // Match a HTML #xxyyzz style parameter
294
        /*if (defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {
395
        /*if (defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {
295
            @_ = ($_[0], hex $1, hex $2, hex $3);
396
            @_ = ($_[0], hex $1, hex $2, hex $3);
296
        }*/
397
        }*/
297
    
398
 
298
        // Check that the colour index is the right range
399
        // Check that the colour index is the right range
299
        if ($index < 8 or $index > 64) {
400
        if ($index < 8 or $index > 64) {
300
            // TODO: assign real error codes
401
            // TODO: assign real error codes
301
            $this->raiseError("Color index $index outside range: 8 <= index <= 64",0,PEAR_ERROR_DIE);
402
            return $this->raiseError("Color index $index outside range: 8 <= index <= 64");
302
        }
403
        }
303
    
404
 
304
        // Check that the colour components are in the right range
405
        // Check that the colour components are in the right range
305
        if ( ($red   < 0 or $red   > 255) or
406
        if (($red   < 0 or $red   > 255) ||
306
             ($green < 0 or $green > 255) or
407
            ($green < 0 or $green > 255) ||
307
             ($blue  < 0 or $blue  > 255) )  
408
            ($blue  < 0 or $blue  > 255))
308
        {
409
        {
309
            $this->raiseError("Color component outside range: 0 <= color <= 255");
410
            return $this->raiseError("Color component outside range: 0 <= color <= 255");
310
        }
411
        }
311
    
412
 
312
        $index -= 8; // Adjust colour index (wingless dragonfly)
413
        $index -= 8; // Adjust colour index (wingless dragonfly)
313
        
414
 
314
        // Set the RGB value
415
        // Set the RGB value
315
        $this->_palette[$index] = array($red, $green, $blue, 0);
416
        $this->_palette[$index] = array($red, $green, $blue, 0);
316
        return($index + 8);
417
        return($index + 8);
317
    }
418
    }
318
    
419
 
319
    /**
420
    /**
320
    * Sets the colour palette to the Excel 97+ default.
421
    * Sets the colour palette to the Excel 97+ default.
321
    *
422
    *
322
    * @access private
423
    * @access private
Line 380... Line 481...
380
                           array(0x99, 0x33, 0x66, 0x00),   // 61
481
                           array(0x99, 0x33, 0x66, 0x00),   // 61
381
                           array(0x33, 0x33, 0x99, 0x00),   // 62
482
                           array(0x33, 0x33, 0x99, 0x00),   // 62
382
                           array(0x33, 0x33, 0x33, 0x00),   // 63
483
                           array(0x33, 0x33, 0x33, 0x00),   // 63
383
                         );
484
                         );
384
    }
485
    }
385
    
486
 
386
    /**
487
    /**
387
    * Assemble worksheets into a workbook and send the BIFF data to an OLE
488
    * Assemble worksheets into a workbook and send the BIFF data to an OLE
388
    * storage.
489
    * storage.
389
    *
490
    *
390
    * @access private
491
    * @access private
-
 
492
    * @return mixed true on success. PEAR_Error on failure
391
    */
493
    */
392
    function _storeWorkbook()
494
    function _storeWorkbook()
393
    {
495
    {
-
 
496
        if (count($this->_worksheets) == 0) {
-
 
497
            return true;
-
 
498
        }
-
 
499
 
394
        // Ensure that at least one worksheet has been selected.
500
        // Ensure that at least one worksheet has been selected.
395
        if ($this->_activesheet == 0)
501
        if ($this->_activesheet == 0) {
396
        {
-
 
397
            $this->_worksheets[0]->selected = 1;
502
            $this->_worksheets[0]->selected = 1;
398
        }
503
        }
399
    
504
 
400
        // Calculate the number of selected worksheet tabs and call the finalization
505
        // Calculate the number of selected worksheet tabs and call the finalization
401
        // methods for each worksheet
506
        // methods for each worksheet
402
        for($i=0; $i < count($this->_worksheets); $i++)
507
        $total_worksheets = count($this->_worksheets);
403
        {
508
        for ($i = 0; $i < $total_worksheets; $i++) {
404
            if($this->_worksheets[$i]->selected) {
509
            if ($this->_worksheets[$i]->selected) {
405
                $this->_selected++;
510
                $this->_selected++;
406
            }
511
            }
407
            $this->_worksheets[$i]->close($this->_sheetnames);
512
            $this->_worksheets[$i]->close($this->_sheetnames);
408
        }
513
        }
409
    
514
 
410
        // Add Workbook globals
515
        // Add Workbook globals
411
        $this->_storeBof(0x0005);
516
        $this->_storeBof(0x0005);
-
 
517
        $this->_storeCodepage();
-
 
518
        if ($this->_BIFF_version == 0x0600) {
-
 
519
            $this->_storeWindow1();
-
 
520
        }
-
 
521
        if ($this->_BIFF_version == 0x0500) {
412
        $this->_storeExterns();    // For print area and repeat rows
522
            $this->_storeExterns();    // For print area and repeat rows
-
 
523
        }
413
        $this->_storeNames();      // For print area and repeat rows
524
        $this->_storeNames();      // For print area and repeat rows
-
 
525
        if ($this->_BIFF_version == 0x0500) {
414
        $this->_storeWindow1();
526
            $this->_storeWindow1();
-
 
527
        }
415
        $this->_store1904();
528
        $this->_storeDatemode();
416
        $this->_storeAllFonts();
529
        $this->_storeAllFonts();
417
        $this->_storeAllNumFormats();
530
        $this->_storeAllNumFormats();
418
        $this->_storeAllXfs();
531
        $this->_storeAllXfs();
419
        $this->_storeAllStyles();
532
        $this->_storeAllStyles();
420
        $this->_storePalette();
533
        $this->_storePalette();
421
        $this->_calcSheetOffsets();
534
        $this->_calcSheetOffsets();
422
    
535
 
423
        // Add BOUNDSHEET records
536
        // Add BOUNDSHEET records
424
        for($i=0; $i < count($this->_worksheets); $i++) {
537
        for ($i = 0; $i < $total_worksheets; $i++) {
425
            $this->_storeBoundsheet($this->_worksheets[$i]->name,$this->_worksheets[$i]->offset);
538
            $this->_storeBoundsheet($this->_worksheets[$i]->name,$this->_worksheets[$i]->offset);
426
        }
539
        }
427
    
540
 
-
 
541
        if ($this->_country_code != -1) {
-
 
542
            $this->_storeCountry();
-
 
543
        }
-
 
544
 
-
 
545
        if ($this->_BIFF_version == 0x0600) {
-
 
546
            //$this->_storeSupbookInternal();
-
 
547
            /* TODO: store external SUPBOOK records and XCT and CRN records
-
 
548
            in case of external references for BIFF8 */
-
 
549
            //$this->_storeExternsheetBiff8();
-
 
550
            $this->_storeSharedStringsTable();
-
 
551
        }
-
 
552
 
428
        // End Workbook globals
553
        // End Workbook globals
429
        $this->_storeEof();
554
        $this->_storeEof();
430
    
555
 
431
        // Store the workbook in an OLE container
556
        // Store the workbook in an OLE container
432
        $this->_storeOLEFile();
557
        $res = $this->_storeOLEFile();
-
 
558
        if ($this->isError($res)) {
-
 
559
            return $this->raiseError($res->getMessage());
-
 
560
        }
-
 
561
        return true;
433
    }
562
    }
434
    
563
 
435
    /**
564
    /**
436
    * Store the workbook in an OLE container if the total size of the workbook data
565
    * Store the workbook in an OLE container
437
    * is less than ~ 7MB.
-
 
438
    *
566
    *
439
    * @access private
567
    * @access private
-
 
568
    * @return mixed true on success. PEAR_Error on failure
440
    */
569
    */
441
    function _storeOLEFile()
570
    function _storeOLEFile()
442
    {
571
    {
-
 
572
        if($this->_BIFF_version == 0x0600) {
443
        $OLE  = new Spreadsheet_Excel_Writer_OLEwriter($this->_filename);
573
            $OLE = new OLE_PPS_File(OLE::Asc2Ucs('Workbook'));
-
 
574
        } else {
444
        $this->_tmp_filename = $OLE->_tmp_filename;
575
            $OLE = new OLE_PPS_File(OLE::Asc2Ucs('Book'));
-
 
576
        }
445
        // Write Worksheet data if data <~ 7MB
577
        if ($this->_tmp_dir != '') {
446
        if ($OLE->setSize($this->_biffsize))
578
            $OLE->setTempDir($this->_tmp_dir);
447
        {
579
        }
448
            $OLE->writeHeader();
580
        $res = $OLE->init();
449
            $OLE->write($this->_data);
581
        if ($this->isError($res)) {
450
            foreach($this->_worksheets as $sheet) 
582
            return $this->raiseError("OLE Error: ".$res->getMessage());
451
            {
583
        }
-
 
584
        $OLE->append($this->_data);
-
 
585
 
-
 
586
        $total_worksheets = count($this->_worksheets);
452
                while ($tmp = $sheet->getData()) {
587
        for ($i = 0; $i < $total_worksheets; $i++) {
453
                    $OLE->write($tmp);
588
            while ($tmp = $this->_worksheets[$i]->getData()) {
454
                }
589
                $OLE->append($tmp);
455
            }
590
            }
456
        }
591
        }
-
 
592
 
-
 
593
        $root = new OLE_PPS_Root(time(), time(), array($OLE));
-
 
594
        if ($this->_tmp_dir != '') {
-
 
595
            $root->setTempDir($this->_tmp_dir);
-
 
596
        }
-
 
597
 
-
 
598
        $res = $root->save($this->_filename);
-
 
599
        if ($this->isError($res)) {
-
 
600
            return $this->raiseError("OLE Error: ".$res->getMessage());
-
 
601
        }
457
        $OLE->close();
602
        return true;
458
    }
603
    }
459
    
604
 
460
    /**
605
    /**
461
    * Calculate offsets for Worksheet BOF records.
606
    * Calculate offsets for Worksheet BOF records.
462
    *
607
    *
463
    * @access private
608
    * @access private
464
    */
609
    */
465
    function _calcSheetOffsets()
610
    function _calcSheetOffsets()
466
    {
611
    {
-
 
612
        if ($this->_BIFF_version == 0x0600) {
-
 
613
            $boundsheet_length = 12;  // fixed length for a BOUNDSHEET record
467
        $BOF     = 11;
614
        } else {
-
 
615
            $boundsheet_length = 11;
-
 
616
        }
468
        $EOF     = 4;
617
        $EOF               = 4;
469
        $offset  = $this->_datasize;
618
        $offset            = $this->_datasize;
-
 
619
 
-
 
620
        if ($this->_BIFF_version == 0x0600) {
-
 
621
            // add the length of the SST
-
 
622
            /* TODO: check this works for a lot of strings (> 8224 bytes) */
-
 
623
            $offset += $this->_calculateSharedStringsSizes();
-
 
624
            if ($this->_country_code != -1) {
-
 
625
                $offset += 8; // adding COUNTRY record
-
 
626
            }
-
 
627
            // add the lenght of SUPBOOK, EXTERNSHEET and NAME records
-
 
628
            //$offset += 8; // FIXME: calculate real value when storing the records
-
 
629
        }
-
 
630
        $total_worksheets = count($this->_worksheets);
-
 
631
        // add the length of the BOUNDSHEET records
470
        for($i=0; $i < count($this->_worksheets); $i++) {
632
        for ($i = 0; $i < $total_worksheets; $i++) {
471
            $offset += $BOF + strlen($this->_worksheets[$i]->name);
633
            $offset += $boundsheet_length + strlen($this->_worksheets[$i]->name);
472
        }
634
        }
473
        $offset += $EOF;
635
        $offset += $EOF;
-
 
636
 
474
        for($i=0; $i < count($this->_worksheets); $i++) {
637
        for ($i = 0; $i < $total_worksheets; $i++) {
475
            $this->_worksheets[$i]->offset = $offset;
638
            $this->_worksheets[$i]->offset = $offset;
476
            $offset += $this->_worksheets[$i]->_datasize;
639
            $offset += $this->_worksheets[$i]->_datasize;
477
        }
640
        }
478
        $this->_biffsize = $offset;
641
        $this->_biffsize = $offset;
479
    }
642
    }
480
    
643
 
481
    /**
644
    /**
482
    * Store the Excel FONT records.
645
    * Store the Excel FONT records.
483
    *
646
    *
484
    * @access private
647
    * @access private
485
    */
648
    */
486
    function _storeAllFonts()
649
    function _storeAllFonts()
487
    {
650
    {
488
        // tmp_format is added by the constructor. We use this to write the default XF's
651
        // tmp_format is added by the constructor. We use this to write the default XF's
489
        $format = $this->_tmp_format;
652
        $format = $this->_tmp_format;
490
        $font   = $format->getFont();
653
        $font   = $format->getFont();
491
    
654
 
492
        // Note: Fonts are 0-indexed. According to the SDK there is no index 4,
655
        // Note: Fonts are 0-indexed. According to the SDK there is no index 4,
493
        // so the following fonts are 0, 1, 2, 3, 5
656
        // so the following fonts are 0, 1, 2, 3, 5
494
        //
657
        //
495
        for($i=1; $i <= 5; $i++){
658
        for ($i = 1; $i <= 5; $i++){
496
            $this->_append($font);
659
            $this->_append($font);
497
        }
660
        }
498
    
661
 
499
        // Iterate through the XF objects and write a FONT record if it isn't the
662
        // Iterate through the XF objects and write a FONT record if it isn't the
500
        // same as the default FONT and if it hasn't already been used.
663
        // same as the default FONT and if it hasn't already been used.
501
        //
664
        //
502
        $fonts = array();
665
        $fonts = array();
503
        $index = 6;                  // The first user defined FONT
666
        $index = 6;                  // The first user defined FONT
504
    
667
 
505
        $key = $format->getFontKey(); // The default font from _tmp_format
668
        $key = $format->getFontKey(); // The default font from _tmp_format
506
        $fonts[$key] = 0;               // Index of the default font
669
        $fonts[$key] = 0;             // Index of the default font
507
    
670
 
-
 
671
        $total_formats = count($this->_formats);
508
        for($i=0; $i < count($this->_formats); $i++) {
672
        for ($i = 0; $i < $total_formats; $i++) {
509
            $key = $this->_formats[$i]->getFontKey();
673
            $key = $this->_formats[$i]->getFontKey();
510
            if (isset($fonts[$key])) {
674
            if (isset($fonts[$key])) {
511
                // FONT has already been used
675
                // FONT has already been used
512
                $this->_formats[$i]->font_index = $fonts[$key];
676
                $this->_formats[$i]->font_index = $fonts[$key];
513
            }
-
 
514
            else {
677
            } else {
515
                // Add a new FONT record
678
                // Add a new FONT record
516
                $fonts[$key]        = $index;
679
                $fonts[$key]        = $index;
517
                $this->_formats[$i]->font_index = $index;
680
                $this->_formats[$i]->font_index = $index;
518
                $index++;
681
                $index++;
519
                $font = $this->_formats[$i]->getFont();
682
                $font = $this->_formats[$i]->getFont();
520
                $this->_append($font);
683
                $this->_append($font);
521
            }
684
            }
522
        }
685
        }
523
    }
686
    }
524
    
687
 
525
    /**
688
    /**
526
    * Store user defined numerical formats i.e. FORMAT records
689
    * Store user defined numerical formats i.e. FORMAT records
527
    *
690
    *
528
    * @access private
691
    * @access private
529
    */
692
    */
Line 531... Line 694...
531
    {
694
    {
532
        // Leaning num_format syndrome
695
        // Leaning num_format syndrome
533
        $hash_num_formats = array();
696
        $hash_num_formats = array();
534
        $num_formats      = array();
697
        $num_formats      = array();
535
        $index = 164;
698
        $index = 164;
536
    
699
 
537
        // Iterate through the XF objects and write a FORMAT record if it isn't a
700
        // Iterate through the XF objects and write a FORMAT record if it isn't a
538
        // built-in format type and if the FORMAT string hasn't already been used.
701
        // built-in format type and if the FORMAT string hasn't already been used.
539
        //
702
        $total_formats = count($this->_formats);
540
        for($i=0; $i < count($this->_formats); $i++)
703
        for ($i = 0; $i < $total_formats; $i++) {
541
        {
-
 
542
            $num_format = $this->_formats[$i]->_num_format;
704
            $num_format = $this->_formats[$i]->_num_format;
543
    
705
 
544
            // Check if $num_format is an index to a built-in format.
706
            // Check if $num_format is an index to a built-in format.
545
            // Also check for a string of zeros, which is a valid format string
707
            // Also check for a string of zeros, which is a valid format string
546
            // but would evaluate to zero.
708
            // but would evaluate to zero.
547
            //
709
            //
548
            if (!preg_match("/^0+\d/",$num_format))
710
            if (!preg_match("/^0+\d/", $num_format)) {
549
            {
-
 
550
                if (preg_match("/^\d+$/",$num_format)) { // built-in format
711
                if (preg_match("/^\d+$/", $num_format)) { // built-in format
551
                    continue;
712
                    continue;
552
                }
713
                }
553
            }
714
            }
554
    
715
 
555
            if (isset($hash_num_formats[$num_format])) {
716
            if (isset($hash_num_formats[$num_format])) {
556
                // FORMAT has already been used
717
                // FORMAT has already been used
557
                $this->_formats[$i]->_num_format = $hash_num_formats[$num_format];
718
                $this->_formats[$i]->_num_format = $hash_num_formats[$num_format];
558
            }
-
 
559
            else{
719
            } else{
560
                // Add a new FORMAT
720
                // Add a new FORMAT
561
                $hash_num_formats[$num_format]  = $index;
721
                $hash_num_formats[$num_format]  = $index;
562
                $this->_formats[$i]->_num_format = $index;
722
                $this->_formats[$i]->_num_format = $index;
563
                array_push($num_formats,$num_format);
723
                array_push($num_formats,$num_format);
564
                $index++;
724
                $index++;
565
            }
725
            }
566
        }
726
        }
567
    
727
 
568
        // Write the new FORMAT records starting from 0xA4
728
        // Write the new FORMAT records starting from 0xA4
569
        $index = 164;
729
        $index = 164;
570
        foreach ($num_formats as $num_format) {
730
        foreach ($num_formats as $num_format) {
571
            $this->_storeNumFormat($num_format,$index);
731
            $this->_storeNumFormat($num_format,$index);
572
            $index++;
732
            $index++;
573
        }
733
        }
574
    }
734
    }
575
    
735
 
576
    /**
736
    /**
577
    * Write all XF records.
737
    * Write all XF records.
578
    *
738
    *
579
    * @access private
739
    * @access private
580
    */
740
    */
Line 582... Line 742...
582
    {
742
    {
583
        // _tmp_format is added by the constructor. We use this to write the default XF's
743
        // _tmp_format is added by the constructor. We use this to write the default XF's
584
        // The default font index is 0
744
        // The default font index is 0
585
        //
745
        //
586
        $format = $this->_tmp_format;
746
        $format = $this->_tmp_format;
587
        for ($i=0; $i <= 14; $i++) {
747
        for ($i = 0; $i <= 14; $i++) {
588
            $xf = $format->getXf('style'); // Style XF
748
            $xf = $format->getXf('style'); // Style XF
589
            $this->_append($xf);
749
            $this->_append($xf);
590
        }
750
        }
591
    
751
 
592
        $xf = $format->getXf('cell');      // Cell XF
752
        $xf = $format->getXf('cell');      // Cell XF
593
        $this->_append($xf);
753
        $this->_append($xf);
594
    
754
 
595
        // User defined XFs
755
        // User defined XFs
-
 
756
        $total_formats = count($this->_formats);
596
        for($i=0; $i < count($this->_formats); $i++) {
757
        for ($i = 0; $i < $total_formats; $i++) {
597
            $xf = $this->_formats[$i]->getXf('cell');
758
            $xf = $this->_formats[$i]->getXf('cell');
598
            $this->_append($xf);
759
            $this->_append($xf);
599
        }
760
        }
600
    }
761
    }
601
    
762
 
602
    /**
763
    /**
603
    * Write all STYLE records.
764
    * Write all STYLE records.
604
    *
765
    *
605
    * @access private 
766
    * @access private
606
    */
767
    */
607
    function _storeAllStyles()
768
    function _storeAllStyles()
608
    {
769
    {
609
        $this->_storeStyle();
770
        $this->_storeStyle();
610
    }
771
    }
611
    
772
 
612
    /**
773
    /**
613
    * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
774
    * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
614
    * the NAME records.
775
    * the NAME records.
615
    *
776
    *
616
    * @access private
777
    * @access private
617
    */
778
    */
618
    function _storeExterns()
779
    function _storeExterns()
-
 
780
 
619
    {
781
    {
620
        // Create EXTERNCOUNT with number of worksheets
782
        // Create EXTERNCOUNT with number of worksheets
621
        $this->_storeExterncount(count($this->_worksheets));
783
        $this->_storeExterncount(count($this->_worksheets));
622
    
784
 
623
        // Create EXTERNSHEET for each worksheet
785
        // Create EXTERNSHEET for each worksheet
624
        foreach ($this->_sheetnames as $sheetname) {
786
        foreach ($this->_sheetnames as $sheetname) {
625
            $this->_storeExternsheet($sheetname);
787
            $this->_storeExternsheet($sheetname);
626
        }
788
        }
627
    }
789
    }
628
    
790
 
629
    /**
791
    /**
630
    * Write the NAME record to define the print area and the repeat rows and cols.
792
    * Write the NAME record to define the print area and the repeat rows and cols.
631
    *
793
    *
632
    * @access private
794
    * @access private
633
    */
795
    */
634
    function _storeNames()
796
    function _storeNames()
635
    {
797
    {
636
        // Create the print area NAME records
798
        // Create the print area NAME records
-
 
799
        $total_worksheets = count($this->_worksheets);
637
        foreach ($this->_worksheets as $worksheet) {
800
        for ($i = 0; $i < $total_worksheets; $i++) {
638
            // Write a Name record if the print area has been defined
801
            // Write a Name record if the print area has been defined
639
            if (isset($worksheet->print_rowmin))
802
            if (isset($this->_worksheets[$i]->print_rowmin)) {
640
            {
-
 
641
                $this->_storeNameShort(
803
                $this->_storeNameShort(
642
                    $worksheet->index,
804
                    $this->_worksheets[$i]->index,
643
                    0x06, // NAME type
805
                    0x06, // NAME type
644
                    $worksheet->print_rowmin,
806
                    $this->_worksheets[$i]->print_rowmin,
645
                    $worksheet->print_rowmax,
807
                    $this->_worksheets[$i]->print_rowmax,
646
                    $worksheet->print_colmin,
808
                    $this->_worksheets[$i]->print_colmin,
647
                    $worksheet->print_colmax
809
                    $this->_worksheets[$i]->print_colmax
648
                    );
810
                    );
649
            }
811
            }
650
        }
812
        }
651
    
813
 
652
        // Create the print title NAME records
814
        // Create the print title NAME records
653
        foreach ($this->_worksheets as $worksheet)
815
        $total_worksheets = count($this->_worksheets);
654
        {
816
        for ($i = 0; $i < $total_worksheets; $i++) {
655
            $rowmin = $worksheet->title_rowmin;
817
            $rowmin = $this->_worksheets[$i]->title_rowmin;
656
            $rowmax = $worksheet->title_rowmax;
818
            $rowmax = $this->_worksheets[$i]->title_rowmax;
657
            $colmin = $worksheet->title_colmin;
819
            $colmin = $this->_worksheets[$i]->title_colmin;
658
            $colmax = $worksheet->title_colmax;
820
            $colmax = $this->_worksheets[$i]->title_colmax;
659
    
821
 
660
            // Determine if row + col, row, col or nothing has been defined
822
            // Determine if row + col, row, col or nothing has been defined
661
            // and write the appropriate record
823
            // and write the appropriate record
662
            //
824
            //
663
            if (isset($rowmin) and isset($colmin)) {
825
            if (isset($rowmin) && isset($colmin)) {
664
                // Row and column titles have been defined.
826
                // Row and column titles have been defined.
665
                // Row title has been defined.
827
                // Row title has been defined.
666
                $this->_storeNameLong(
828
                $this->_storeNameLong(
667
                    $worksheet->index,
829
                    $this->_worksheets[$i]->index,
668
                    0x07, // NAME type
830
                    0x07, // NAME type
669
                    $rowmin,
831
                    $rowmin,
670
                    $rowmax,
832
                    $rowmax,
671
                    $colmin,
833
                    $colmin,
672
                    $colmax
834
                    $colmax
673
                    );
835
                    );
674
            }
-
 
675
            elseif (isset($rowmin)) {
836
            } elseif (isset($rowmin)) {
676
                // Row title has been defined.
837
                // Row title has been defined.
677
                $this->_storeNameShort(
838
                $this->_storeNameShort(
678
                    $worksheet->index,
839
                    $this->_worksheets[$i]->index,
679
                    0x07, // NAME type
840
                    0x07, // NAME type
680
                    $rowmin,
841
                    $rowmin,
681
                    $rowmax,
842
                    $rowmax,
682
                    0x00,
843
                    0x00,
683
                    0xff
844
                    0xff
684
                    );
845
                    );
685
            }
-
 
686
            elseif (isset($colmin)) {
846
            } elseif (isset($colmin)) {
687
                // Column title has been defined.
847
                // Column title has been defined.
688
                $this->_storeNameShort(
848
                $this->_storeNameShort(
689
                    $worksheet->index,
849
                    $this->_worksheets[$i]->index,
690
                    0x07, // NAME type
850
                    0x07, // NAME type
691
                    0x0000,
851
                    0x0000,
692
                    0x3fff,
852
                    0x3fff,
693
                    $colmin,
853
                    $colmin,
694
                    $colmax
854
                    $colmax
695
                    );
855
                    );
696
            }
-
 
697
            else {
856
            } else {
698
                // Print title hasn't been defined.
857
                // Print title hasn't been defined.
699
            }
858
            }
700
        }
859
        }
701
    }
860
    }
702
    
861
 
703
    
862
 
704
    
863
 
705
    
864
 
706
    /******************************************************************************
865
    /******************************************************************************
707
    *
866
    *
708
    * BIFF RECORDS
867
    * BIFF RECORDS
709
    *
868
    *
710
    */
869
    */
711
    
870
 
-
 
871
    /**
-
 
872
    * Stores the CODEPAGE biff record.
-
 
873
    *
-
 
874
    * @access private
-
 
875
    */
-
 
876
    function _storeCodepage()
-
 
877
    {
-
 
878
        $record          = 0x0042;             // Record identifier
-
 
879
        $length          = 0x0002;             // Number of bytes to follow
-
 
880
        $cv              = $this->_codepage;   // The code page
-
 
881
 
-
 
882
        $header          = pack('vv', $record, $length);
-
 
883
        $data            = pack('v',  $cv);
-
 
884
 
-
 
885
        $this->_append($header . $data);
-
 
886
    }
-
 
887
 
712
    /**
888
    /**
713
    * Write Excel BIFF WINDOW1 record.
889
    * Write Excel BIFF WINDOW1 record.
714
    *
890
    *
715
    * @access private
891
    * @access private
716
    */
892
    */
717
    function _storeWindow1()
893
    function _storeWindow1()
718
    {
894
    {
719
        $record    = 0x003D;                 // Record identifier
895
        $record    = 0x003D;                 // Record identifier
720
        $length    = 0x0012;                 // Number of bytes to follow
896
        $length    = 0x0012;                 // Number of bytes to follow
721
    
897
 
722
        $xWn       = 0x0000;                 // Horizontal position of window
898
        $xWn       = 0x0000;                 // Horizontal position of window
723
        $yWn       = 0x0000;                 // Vertical position of window
899
        $yWn       = 0x0000;                 // Vertical position of window
724
        $dxWn      = 0x25BC;                 // Width of window
900
        $dxWn      = 0x25BC;                 // Width of window
725
        $dyWn      = 0x1572;                 // Height of window
901
        $dyWn      = 0x1572;                 // Height of window
726
    
902
 
727
        $grbit     = 0x0038;                 // Option flags
903
        $grbit     = 0x0038;                 // Option flags
728
        $ctabsel   = $this->_selected;       // Number of workbook tabs selected
904
        $ctabsel   = $this->_selected;       // Number of workbook tabs selected
729
        $wTabRatio = 0x0258;                 // Tab to scrollbar ratio
905
        $wTabRatio = 0x0258;                 // Tab to scrollbar ratio
730
    
906
 
731
        $itabFirst = $this->_firstsheet;     // 1st displayed worksheet
907
        $itabFirst = $this->_firstsheet;     // 1st displayed worksheet
732
        $itabCur   = $this->_activesheet;    // Active worksheet
908
        $itabCur   = $this->_activesheet;    // Active worksheet
733
    
909
 
734
        $header    = pack("vv",        $record, $length);
910
        $header    = pack("vv",        $record, $length);
735
        $data      = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn,
911
        $data      = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn,
736
                                       $grbit,
912
                                       $grbit,
737
                                       $itabCur, $itabFirst,
913
                                       $itabCur, $itabFirst,
738
                                       $ctabsel, $wTabRatio);
914
                                       $ctabsel, $wTabRatio);
739
        $this->_append($header.$data);
915
        $this->_append($header . $data);
740
    }
916
    }
741
    
917
 
742
    /**
918
    /**
743
    * Writes Excel BIFF BOUNDSHEET record.
919
    * Writes Excel BIFF BOUNDSHEET record.
-
 
920
    * FIXME: inconsistent with BIFF documentation
744
    *
921
    *
745
    * @param string  $sheetname Worksheet name
922
    * @param string  $sheetname Worksheet name
746
    * @param integer $offset    Location of worksheet BOF
923
    * @param integer $offset    Location of worksheet BOF
747
    * @access private
924
    * @access private
748
    */
925
    */
749
    function _storeBoundsheet($sheetname,$offset)
926
    function _storeBoundsheet($sheetname,$offset)
750
    {
927
    {
751
        $record    = 0x0085;                    // Record identifier
928
        $record    = 0x0085;                    // Record identifier
-
 
929
        if ($this->_BIFF_version == 0x0600) {
752
        $length    = 0x07 + strlen($sheetname); // Number of bytes to follow
930
            $length    = 0x08 + strlen($sheetname); // Number of bytes to follow
-
 
931
        } else {
-
 
932
            $length = 0x07 + strlen($sheetname); // Number of bytes to follow
-
 
933
        }
753
    
934
 
754
        $grbit     = 0x0000;                    // Sheet identifier
935
        $grbit     = 0x0000;                    // Visibility and sheet type
-
 
936
        if ($this->_BIFF_version == 0x0600) {
-
 
937
            $cch       = mb_strlen($sheetname,'UTF-16LE'); // Length of sheet name
-
 
938
        } else {
755
        $cch       = strlen($sheetname);        // Length of sheet name
939
            $cch       = strlen($sheetname);        // Length of sheet name
-
 
940
        }
756
    
941
 
757
        $header    = pack("vv",  $record, $length);
942
        $header    = pack("vv",  $record, $length);
-
 
943
        if ($this->_BIFF_version == 0x0600) {
-
 
944
            $data      = pack("VvCC", $offset, $grbit, $cch, 0x1);
-
 
945
        } else {
758
        $data      = pack("VvC", $offset, $grbit, $cch);
946
            $data      = pack("VvC", $offset, $grbit, $cch);
-
 
947
        }
759
        $this->_append($header.$data.$sheetname);
948
        $this->_append($header.$data.$sheetname);
760
    }
949
    }
-
 
950
 
-
 
951
    /**
-
 
952
    * Write Internal SUPBOOK record
-
 
953
    *
-
 
954
    * @access private
-
 
955
    */
-
 
956
    function _storeSupbookInternal()
-
 
957
    {
-
 
958
        $record    = 0x01AE;   // Record identifier
-
 
959
        $length    = 0x0004;   // Bytes to follow
-
 
960
 
-
 
961
        $header    = pack("vv", $record, $length);
-
 
962
        $data      = pack("vv", count($this->_worksheets), 0x0104);
-
 
963
        $this->_append($header . $data);
-
 
964
    }
-
 
965
 
-
 
966
    /**
-
 
967
    * Writes the Excel BIFF EXTERNSHEET record. These references are used by
-
 
968
    * formulas.
-
 
969
    *
-
 
970
    * @param string $sheetname Worksheet name
-
 
971
    * @access private
-
 
972
    */
-
 
973
    function _storeExternsheetBiff8()
-
 
974
    {
-
 
975
        $total_references = count($this->_parser->_references);
-
 
976
        $record   = 0x0017;                     // Record identifier
-
 
977
        $length   = 2 + 6 * $total_references;  // Number of bytes to follow
-
 
978
 
-
 
979
        $supbook_index = 0;           // FIXME: only using internal SUPBOOK record
-
 
980
        $header           = pack("vv",  $record, $length);
-
 
981
        $data             = pack('v', $total_references);
-
 
982
        for ($i = 0; $i < $total_references; $i++) {
-
 
983
            $data .= $this->_parser->_references[$i];
-
 
984
        }
-
 
985
        $this->_append($header . $data);
-
 
986
    }
761
    
987
 
762
    /**
988
    /**
763
    * Write Excel BIFF STYLE records.
989
    * Write Excel BIFF STYLE records.
764
    *
990
    *
765
    * @access private
991
    * @access private
766
    */
992
    */
767
    function _storeStyle()
993
    function _storeStyle()
768
    {
994
    {
769
        $record    = 0x0293;   // Record identifier
995
        $record    = 0x0293;   // Record identifier
770
        $length    = 0x0004;   // Bytes to follow
996
        $length    = 0x0004;   // Bytes to follow
771
                               
997
 
772
        $ixfe      = 0x8000;   // Index to style XF
998
        $ixfe      = 0x8000;   // Index to style XF
773
        $BuiltIn   = 0x00;     // Built-in style
999
        $BuiltIn   = 0x00;     // Built-in style
774
        $iLevel    = 0xff;     // Outline style level
1000
        $iLevel    = 0xff;     // Outline style level
775
    
1001
 
776
        $header    = pack("vv",  $record, $length);
1002
        $header    = pack("vv",  $record, $length);
777
        $data      = pack("vCC", $ixfe, $BuiltIn, $iLevel);
1003
        $data      = pack("vCC", $ixfe, $BuiltIn, $iLevel);
778
        $this->_append($header.$data);
1004
        $this->_append($header . $data);
779
    }
1005
    }
780
    
1006
 
781
    
1007
 
782
    /**
1008
    /**
783
    * Writes Excel FORMAT record for non "built-in" numerical formats.
1009
    * Writes Excel FORMAT record for non "built-in" numerical formats.
784
    *
1010
    *
785
    * @param string  $format Custom format string
1011
    * @param string  $format Custom format string
786
    * @param integer $ifmt   Format index code
1012
    * @param integer $ifmt   Format index code
787
    * @access private
1013
    * @access private
788
    */
1014
    */
789
    function _storeNumFormat($format,$ifmt)
1015
    function _storeNumFormat($format, $ifmt)
790
    {
1016
    {
791
        $record    = 0x041E;                      // Record identifier
1017
        $record    = 0x041E;                      // Record identifier
-
 
1018
 
-
 
1019
        if ($this->_BIFF_version == 0x0600) {
-
 
1020
            $length    = 5 + strlen($format);      // Number of bytes to follow
-
 
1021
            $encoding = 0x0;
-
 
1022
        } elseif ($this->_BIFF_version == 0x0500) {
792
        $length    = 0x03 + strlen($format);      // Number of bytes to follow
1023
            $length    = 3 + strlen($format);      // Number of bytes to follow
-
 
1024
        }
793
    
1025
 
-
 
1026
        if ( $this->_BIFF_version == 0x0600 && function_exists('iconv') ) {     // Encode format String
-
 
1027
            if (mb_detect_encoding($format, 'auto') !== 'UTF-16LE'){
-
 
1028
                $format = iconv(mb_detect_encoding($format, 'auto'),'UTF-16LE',$format);
-
 
1029
            }
-
 
1030
            $encoding = 1;
-
 
1031
            $cch = function_exists('mb_strlen') ? mb_strlen($format, 'UTF-16LE') : (strlen($format) / 2);
-
 
1032
        } else {
-
 
1033
            $encoding = 0;
794
        $cch       = strlen($format);             // Length of format string
1034
            $cch  = strlen($format);             // Length of format string
-
 
1035
        }
-
 
1036
        $length = strlen($format);
795
    
1037
 
-
 
1038
        if ($this->_BIFF_version == 0x0600) {
-
 
1039
            $header    = pack("vv", $record, 5 + $length);
-
 
1040
            $data      = pack("vvC", $ifmt, $cch, $encoding);
-
 
1041
        } elseif ($this->_BIFF_version == 0x0500) {
796
        $header    = pack("vv", $record, $length);
1042
            $header    = pack("vv", $record, 3 + $length);
797
        $data      = pack("vC", $ifmt, $cch);
1043
            $data      = pack("vC", $ifmt, $cch);
-
 
1044
        }
798
        $this->_append($header.$data.$format);
1045
        $this->_append($header . $data . $format);
799
    }
1046
    }
800
    
1047
 
801
    /**
1048
    /**
802
    * Write Excel 1904 record to indicate the date system in use.
1049
    * Write DATEMODE record to indicate the date system in use (1904 or 1900).
803
    *
1050
    *
804
    * @access private
1051
    * @access private
805
    */
1052
    */
806
    function _store1904()
1053
    function _storeDatemode()
807
    {
1054
    {
808
        $record    = 0x0022;         // Record identifier
1055
        $record    = 0x0022;         // Record identifier
809
        $length    = 0x0002;         // Bytes to follow
1056
        $length    = 0x0002;         // Bytes to follow
810
    
1057
 
811
        $f1904     = $this->_1904;   // Flag for 1904 date system
1058
        $f1904     = $this->_1904;   // Flag for 1904 date system
812
    
1059
 
813
        $header    = pack("vv", $record, $length);
1060
        $header    = pack("vv", $record, $length);
814
        $data      = pack("v", $f1904);
1061
        $data      = pack("v", $f1904);
815
        $this->_append($header.$data);
1062
        $this->_append($header . $data);
816
    }
1063
    }
817
    
1064
 
818
    
1065
 
819
    /**
1066
    /**
820
    * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
1067
    * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
821
    * references in the workbook.
1068
    * references in the workbook.
822
    *
1069
    *
823
    * Excel only stores references to external sheets that are used in NAME.
1070
    * Excel only stores references to external sheets that are used in NAME.
Line 831... Line 1078...
831
    */
1078
    */
832
    function _storeExterncount($cxals)
1079
    function _storeExterncount($cxals)
833
    {
1080
    {
834
        $record   = 0x0016;          // Record identifier
1081
        $record   = 0x0016;          // Record identifier
835
        $length   = 0x0002;          // Number of bytes to follow
1082
        $length   = 0x0002;          // Number of bytes to follow
836
    
1083
 
837
        $header   = pack("vv", $record, $length);
1084
        $header   = pack("vv", $record, $length);
838
        $data     = pack("v",  $cxals);
1085
        $data     = pack("v",  $cxals);
839
        $this->_append($header.$data);
1086
        $this->_append($header . $data);
840
    }
1087
    }
841
    
1088
 
842
    
1089
 
843
    /**
1090
    /**
844
    * Writes the Excel BIFF EXTERNSHEET record. These references are used by
1091
    * Writes the Excel BIFF EXTERNSHEET record. These references are used by
845
    * formulas. NAME record is required to define the print area and the repeat
1092
    * formulas. NAME record is required to define the print area and the repeat
846
    * rows and columns.
1093
    * rows and columns.
847
    *
1094
    *
Line 852... Line 1099...
852
    */
1099
    */
853
    function _storeExternsheet($sheetname)
1100
    function _storeExternsheet($sheetname)
854
    {
1101
    {
855
        $record      = 0x0017;                     // Record identifier
1102
        $record      = 0x0017;                     // Record identifier
856
        $length      = 0x02 + strlen($sheetname);  // Number of bytes to follow
1103
        $length      = 0x02 + strlen($sheetname);  // Number of bytes to follow
857
                                                   
1104
 
858
        $cch         = strlen($sheetname);         // Length of sheet name
1105
        $cch         = strlen($sheetname);         // Length of sheet name
859
        $rgch        = 0x03;                       // Filename encoding
1106
        $rgch        = 0x03;                       // Filename encoding
860
    
1107
 
861
        $header      = pack("vv",  $record, $length);
1108
        $header      = pack("vv",  $record, $length);
862
        $data        = pack("CC", $cch, $rgch);
1109
        $data        = pack("CC", $cch, $rgch);
863
        $this->_append($header.$data.$sheetname);
1110
        $this->_append($header . $data . $sheetname);
864
    }
1111
    }
865
    
1112
 
866
    
1113
 
867
    /**
1114
    /**
868
    * Store the NAME record in the short format that is used for storing the print
1115
    * Store the NAME record in the short format that is used for storing the print
869
    * area, repeat rows only and repeat columns only.
1116
    * area, repeat rows only and repeat columns only.
870
    *
1117
    *
871
    * @param integer $index  Sheet index
1118
    * @param integer $index  Sheet index
Line 874... Line 1121...
874
    * @param integer $rowmax End row
1121
    * @param integer $rowmax End row
875
    * @param integer $colmin Start colum
1122
    * @param integer $colmin Start colum
876
    * @param integer $colmax End column
1123
    * @param integer $colmax End column
877
    * @access private
1124
    * @access private
878
    */
1125
    */
879
    function _storeNameShort($index,$type,$rowmin,$rowmax,$colmin,$colmax)
1126
    function _storeNameShort($index, $type, $rowmin, $rowmax, $colmin, $colmax)
880
    {
1127
    {
881
        $record          = 0x0018;       // Record identifier
1128
        $record          = 0x0018;       // Record identifier
882
        $length          = 0x0024;       // Number of bytes to follow
1129
        $length          = 0x0024;       // Number of bytes to follow
883
    
1130
 
884
        $grbit           = 0x0020;       // Option flags
1131
        $grbit           = 0x0020;       // Option flags
885
        $chKey           = 0x00;         // Keyboard shortcut
1132
        $chKey           = 0x00;         // Keyboard shortcut
886
        $cch             = 0x01;         // Length of text name
1133
        $cch             = 0x01;         // Length of text name
887
        $cce             = 0x0015;       // Length of text definition
1134
        $cce             = 0x0015;       // Length of text definition
888
        $ixals           = $index + 1;   // Sheet index
1135
        $ixals           = $index + 1;   // Sheet index
Line 890... Line 1137...
890
        $cchCustMenu     = 0x00;         // Length of cust menu text
1137
        $cchCustMenu     = 0x00;         // Length of cust menu text
891
        $cchDescription  = 0x00;         // Length of description text
1138
        $cchDescription  = 0x00;         // Length of description text
892
        $cchHelptopic    = 0x00;         // Length of help topic text
1139
        $cchHelptopic    = 0x00;         // Length of help topic text
893
        $cchStatustext   = 0x00;         // Length of status bar text
1140
        $cchStatustext   = 0x00;         // Length of status bar text
894
        $rgch            = $type;        // Built-in name type
1141
        $rgch            = $type;        // Built-in name type
895
    
1142
 
896
        $unknown03       = 0x3b;
1143
        $unknown03       = 0x3b;
897
        $unknown04       = 0xffff-$index;
1144
        $unknown04       = 0xffff-$index;
898
        $unknown05       = 0x0000;
1145
        $unknown05       = 0x0000;
899
        $unknown06       = 0x0000;
1146
        $unknown06       = 0x0000;
900
        $unknown07       = 0x1087;
1147
        $unknown07       = 0x1087;
901
        $unknown08       = 0x8005;
1148
        $unknown08       = 0x8005;
902
    
1149
 
903
        $header             = pack("vv", $record, $length);
1150
        $header             = pack("vv", $record, $length);
904
        $data               = pack("v", $grbit);
1151
        $data               = pack("v", $grbit);
905
        $data              .= pack("C", $chKey);
1152
        $data              .= pack("C", $chKey);
906
        $data              .= pack("C", $cch);
1153
        $data              .= pack("C", $cch);
907
        $data              .= pack("v", $cce);
1154
        $data              .= pack("v", $cce);
Line 922... Line 1169...
922
        $data              .= pack("v", $index);
1169
        $data              .= pack("v", $index);
923
        $data              .= pack("v", $rowmin);
1170
        $data              .= pack("v", $rowmin);
924
        $data              .= pack("v", $rowmax);
1171
        $data              .= pack("v", $rowmax);
925
        $data              .= pack("C", $colmin);
1172
        $data              .= pack("C", $colmin);
926
        $data              .= pack("C", $colmax);
1173
        $data              .= pack("C", $colmax);
927
        $this->_append($header.$data);
1174
        $this->_append($header . $data);
928
    }
1175
    }
929
    
1176
 
930
    
1177
 
931
    /**
1178
    /**
932
    * Store the NAME record in the long format that is used for storing the repeat
1179
    * Store the NAME record in the long format that is used for storing the repeat
933
    * rows and columns when both are specified. This shares a lot of code with
1180
    * rows and columns when both are specified. This shares a lot of code with
934
    * _storeNameShort() but we use a separate method to keep the code clean.
1181
    * _storeNameShort() but we use a separate method to keep the code clean.
935
    * Code abstraction for reuse can be carried too far, and I should know. ;-)
1182
    * Code abstraction for reuse can be carried too far, and I should know. ;-)
Line 940... Line 1187...
940
    * @param integer $rowmax End row
1187
    * @param integer $rowmax End row
941
    * @param integer $colmin Start colum
1188
    * @param integer $colmin Start colum
942
    * @param integer $colmax End column
1189
    * @param integer $colmax End column
943
    * @access private
1190
    * @access private
944
    */
1191
    */
945
    function _storeNameLong($index,$type,$rowmin,$rowmax,$colmin,$colmax)
1192
    function _storeNameLong($index, $type, $rowmin, $rowmax, $colmin, $colmax)
946
    {
1193
    {
947
        $record          = 0x0018;       // Record identifier
1194
        $record          = 0x0018;       // Record identifier
948
        $length          = 0x003d;       // Number of bytes to follow
1195
        $length          = 0x003d;       // Number of bytes to follow
949
        $grbit           = 0x0020;       // Option flags
1196
        $grbit           = 0x0020;       // Option flags
950
        $chKey           = 0x00;         // Keyboard shortcut
1197
        $chKey           = 0x00;         // Keyboard shortcut
Line 955... Line 1202...
955
        $cchCustMenu     = 0x00;         // Length of cust menu text
1202
        $cchCustMenu     = 0x00;         // Length of cust menu text
956
        $cchDescription  = 0x00;         // Length of description text
1203
        $cchDescription  = 0x00;         // Length of description text
957
        $cchHelptopic    = 0x00;         // Length of help topic text
1204
        $cchHelptopic    = 0x00;         // Length of help topic text
958
        $cchStatustext   = 0x00;         // Length of status bar text
1205
        $cchStatustext   = 0x00;         // Length of status bar text
959
        $rgch            = $type;        // Built-in name type
1206
        $rgch            = $type;        // Built-in name type
960
    
1207
 
961
        $unknown01       = 0x29;
1208
        $unknown01       = 0x29;
962
        $unknown02       = 0x002b;
1209
        $unknown02       = 0x002b;
963
        $unknown03       = 0x3b;
1210
        $unknown03       = 0x3b;
964
        $unknown04       = 0xffff-$index;
1211
        $unknown04       = 0xffff-$index;
965
        $unknown05       = 0x0000;
1212
        $unknown05       = 0x0000;
966
        $unknown06       = 0x0000;
1213
        $unknown06       = 0x0000;
967
        $unknown07       = 0x1087;
1214
        $unknown07       = 0x1087;
968
        $unknown08       = 0x8008;
1215
        $unknown08       = 0x8008;
969
    
1216
 
970
        $header             = pack("vv",  $record, $length);
1217
        $header             = pack("vv",  $record, $length);
971
        $data               = pack("v", $grbit);
1218
        $data               = pack("v", $grbit);
972
        $data              .= pack("C", $chKey);
1219
        $data              .= pack("C", $chKey);
973
        $data              .= pack("C", $cch);
1220
        $data              .= pack("C", $cch);
974
        $data              .= pack("v", $cce);
1221
        $data              .= pack("v", $cce);
Line 1007... Line 1254...
1007
        $data              .= pack("v", $rowmax);
1254
        $data              .= pack("v", $rowmax);
1008
        $data              .= pack("C", 0x00);
1255
        $data              .= pack("C", 0x00);
1009
        $data              .= pack("C", 0xff);
1256
        $data              .= pack("C", 0xff);
1010
        // End of data
1257
        // End of data
1011
        $data              .= pack("C", 0x10);
1258
        $data              .= pack("C", 0x10);
1012
        $this->_append($header.$data);
1259
        $this->_append($header . $data);
1013
    }
1260
    }
1014
    
1261
 
-
 
1262
    /**
-
 
1263
    * Stores the COUNTRY record for localization
-
 
1264
    *
-
 
1265
    * @access private
-
 
1266
    */
-
 
1267
    function _storeCountry()
-
 
1268
    {
-
 
1269
        $record          = 0x008C;    // Record identifier
-
 
1270
        $length          = 4;         // Number of bytes to follow
-
 
1271
 
-
 
1272
        $header = pack('vv',  $record, $length);
-
 
1273
        /* using the same country code always for simplicity */
-
 
1274
        $data = pack('vv', $this->_country_code, $this->_country_code);
-
 
1275
        $this->_append($header . $data);
-
 
1276
    }
1015
    
1277
 
1016
    /**
1278
    /**
1017
    * Stores the PALETTE biff record.
1279
    * Stores the PALETTE biff record.
1018
    *
1280
    *
1019
    * @access private
1281
    * @access private
1020
    */
1282
    */
1021
    function _storePalette()
1283
    function _storePalette()
1022
    {
1284
    {
1023
        $aref            = $this->_palette;
1285
        $aref            = $this->_palette;
1024
    
1286
 
1025
        $record          = 0x0092;                 // Record identifier
1287
        $record          = 0x0092;                 // Record identifier
1026
        $length          = 2 + 4 * count($aref);   // Number of bytes to follow
1288
        $length          = 2 + 4 * count($aref);   // Number of bytes to follow
1027
        $ccv             =         count($aref);   // Number of RGB values to follow
1289
        $ccv             =         count($aref);   // Number of RGB values to follow
1028
        $data = '';                                // The RGB data
1290
        $data = '';                                // The RGB data
1029
    
1291
 
1030
        // Pack the RGB data
1292
        // Pack the RGB data
1031
        foreach($aref as $color)
1293
        foreach ($aref as $color) {
1032
        {
-
 
1033
            foreach($color as $byte) {
1294
            foreach ($color as $byte) {
1034
                $data .= pack("C",$byte);
1295
                $data .= pack("C",$byte);
1035
            }
1296
            }
1036
        }
1297
        }
1037
    
1298
 
1038
        $header = pack("vvv",  $record, $length, $ccv);
1299
        $header = pack("vvv",  $record, $length, $ccv);
1039
        $this->_append($header.$data);
1300
        $this->_append($header . $data);
1040
    }
1301
    }
-
 
1302
 
-
 
1303
    /**
-
 
1304
    * Calculate
-
 
1305
    * Handling of the SST continue blocks is complicated by the need to include an
-
 
1306
    * additional continuation byte depending on whether the string is split between
-
 
1307
    * blocks or whether it starts at the beginning of the block. (There are also
-
 
1308
    * additional complications that will arise later when/if Rich Strings are
-
 
1309
    * supported).
-
 
1310
    *
-
 
1311
    * @access private
-
 
1312
    */
-
 
1313
    function _calculateSharedStringsSizes()
-
 
1314
    {
-
 
1315
        /* Iterate through the strings to calculate the CONTINUE block sizes.
-
 
1316
           For simplicity we use the same size for the SST and CONTINUE records:
-
 
1317
           8228 : Maximum Excel97 block size
-
 
1318
             -4 : Length of block header
-
 
1319
             -8 : Length of additional SST header information
-
 
1320
             -8 : Arbitrary number to keep within _add_continue() limit = 8208
-
 
1321
        */
-
 
1322
        $continue_limit     = 8208;
-
 
1323
        $block_length       = 0;
-
 
1324
        $written            = 0;
-
 
1325
        $this->_block_sizes = array();
-
 
1326
        $continue           = 0;
-
 
1327
 
-
 
1328
        foreach (array_keys($this->_str_table) as $string) {
-
 
1329
            $string_length = strlen($string);
-
 
1330
            $headerinfo    = unpack("vlength/Cencoding", $string);
-
 
1331
            $encoding      = $headerinfo["encoding"];
-
 
1332
            $split_string  = 0;
-
 
1333
 
-
 
1334
            // Block length is the total length of the strings that will be
-
 
1335
            // written out in a single SST or CONTINUE block.
-
 
1336
            $block_length += $string_length;
-
 
1337
 
-
 
1338
            // We can write the string if it doesn't cross a CONTINUE boundary
-
 
1339
            if ($block_length < $continue_limit) {
-
 
1340
                $written      += $string_length;
-
 
1341
                continue;
-
 
1342
            }
-
 
1343
 
-
 
1344
            // Deal with the cases where the next string to be written will exceed
-
 
1345
            // the CONTINUE boundary. If the string is very long it may need to be
-
 
1346
            // written in more than one CONTINUE record.
-
 
1347
            while ($block_length >= $continue_limit) {
-
 
1348
 
-
 
1349
                // We need to avoid the case where a string is continued in the first
-
 
1350
                // n bytes that contain the string header information.
-
 
1351
                $header_length   = 3; // Min string + header size -1
-
 
1352
                $space_remaining = $continue_limit - $written - $continue;
-
 
1353
 
-
 
1354
 
-
 
1355
                /* TODO: Unicode data should only be split on char (2 byte)
-
 
1356
                boundaries. Therefore, in some cases we need to reduce the
-
 
1357
                amount of available
-
 
1358
                */
-
 
1359
                $align = 0;
-
 
1360
 
-
 
1361
                // Only applies to Unicode strings
-
 
1362
                if ($encoding == 1) {
-
 
1363
                    // Min string + header size -1
-
 
1364
                    $header_length = 4;
-
 
1365
 
-
 
1366
                    if ($space_remaining > $header_length) {
-
 
1367
                        // String contains 3 byte header => split on odd boundary
-
 
1368
                        if (!$split_string && $space_remaining % 2 != 1) {
-
 
1369
                            $space_remaining--;
-
 
1370
                            $align = 1;
-
 
1371
                        }
-
 
1372
                        // Split section without header => split on even boundary
-
 
1373
                        else if ($split_string && $space_remaining % 2 == 1) {
-
 
1374
                            $space_remaining--;
-
 
1375
                            $align = 1;
-
 
1376
                        }
-
 
1377
 
-
 
1378
                        $split_string = 1;
-
 
1379
                    }
-
 
1380
                }
-
 
1381
 
-
 
1382
 
-
 
1383
                if ($space_remaining > $header_length) {
-
 
1384
                    // Write as much as possible of the string in the current block
-
 
1385
                    $written      += $space_remaining;
-
 
1386
 
-
 
1387
                    // Reduce the current block length by the amount written
-
 
1388
                    $block_length -= $continue_limit - $continue - $align;
-
 
1389
 
-
 
1390
                    // Store the max size for this block
-
 
1391
                    $this->_block_sizes[] = $continue_limit - $align;
-
 
1392
 
-
 
1393
                    // If the current string was split then the next CONTINUE block
-
 
1394
                    // should have the string continue flag (grbit) set unless the
-
 
1395
                    // split string fits exactly into the remaining space.
-
 
1396
                    if ($block_length > 0) {
-
 
1397
                        $continue = 1;
-
 
1398
                    } else {
-
 
1399
                        $continue = 0;
-
 
1400
                    }
-
 
1401
                } else {
-
 
1402
                    // Store the max size for this block
-
 
1403
                    $this->_block_sizes[] = $written + $continue;
-
 
1404
 
-
 
1405
                    // Not enough space to start the string in the current block
-
 
1406
                    $block_length -= $continue_limit - $space_remaining - $continue;
-
 
1407
                    $continue = 0;
-
 
1408
 
-
 
1409
                }
-
 
1410
 
-
 
1411
                // If the string (or substr) is small enough we can write it in the
-
 
1412
                // new CONTINUE block. Else, go through the loop again to write it in
-
 
1413
                // one or more CONTINUE blocks
-
 
1414
                if ($block_length < $continue_limit) {
-
 
1415
                    $written = $block_length;
-
 
1416
                } else {
-
 
1417
                    $written = 0;
-
 
1418
                }
-
 
1419
            }
-
 
1420
        }
-
 
1421
 
-
 
1422
        // Store the max size for the last block unless it is empty
-
 
1423
        if ($written + $continue) {
-
 
1424
            $this->_block_sizes[] = $written + $continue;
-
 
1425
        }
-
 
1426
 
-
 
1427
 
-
 
1428
        /* Calculate the total length of the SST and associated CONTINUEs (if any).
-
 
1429
         The SST record will have a length even if it contains no strings.
-
 
1430
         This length is required to set the offsets in the BOUNDSHEET records since
-
 
1431
         they must be written before the SST records
-
 
1432
        */
-
 
1433
 
-
 
1434
        $tmp_block_sizes = array();
-
 
1435
        $tmp_block_sizes = $this->_block_sizes;
-
 
1436
 
-
 
1437
        $length  = 12;
-
 
1438
        if (!empty($tmp_block_sizes)) {
-
 
1439
            $length += array_shift($tmp_block_sizes); // SST
-
 
1440
        }
-
 
1441
        while (!empty($tmp_block_sizes)) {
-
 
1442
            $length += 4 + array_shift($tmp_block_sizes); // CONTINUEs
-
 
1443
        }
-
 
1444
 
-
 
1445
        return $length;
-
 
1446
    }
-
 
1447
 
-
 
1448
    /**
-
 
1449
    * Write all of the workbooks strings into an indexed array.
-
 
1450
    * See the comments in _calculate_shared_string_sizes() for more information.
-
 
1451
    *
-
 
1452
    * The Excel documentation says that the SST record should be followed by an
-
 
1453
    * EXTSST record. The EXTSST record is a hash table that is used to optimise
-
 
1454
    * access to SST. However, despite the documentation it doesn't seem to be
-
 
1455
    * required so we will ignore it.
-
 
1456
    *
-
 
1457
    * @access private
-
 
1458
    */
-
 
1459
    function _storeSharedStringsTable()
-
 
1460
    {
-
 
1461
        $record  = 0x00fc;  // Record identifier
-
 
1462
        $length  = 0x0008;  // Number of bytes to follow
-
 
1463
        $total   = 0x0000;
-
 
1464
 
-
 
1465
        // Iterate through the strings to calculate the CONTINUE block sizes
-
 
1466
        $continue_limit = 8208;
-
 
1467
        $block_length   = 0;
-
 
1468
        $written        = 0;
-
 
1469
        $continue       = 0;
-
 
1470
 
-
 
1471
        // sizes are upside down
-
 
1472
        $tmp_block_sizes = $this->_block_sizes;
-
 
1473
        // $tmp_block_sizes = array_reverse($this->_block_sizes);
-
 
1474
 
-
 
1475
        // The SST record is required even if it contains no strings. Thus we will
-
 
1476
        // always have a length
-
 
1477
        //
-
 
1478
        if (!empty($tmp_block_sizes)) {
-
 
1479
            $length = 8 + array_shift($tmp_block_sizes);
-
 
1480
        }
-
 
1481
        else {
-
 
1482
            // No strings
-
 
1483
            $length = 8;
-
 
1484
        }
-
 
1485
 
-
 
1486
 
-
 
1487
 
-
 
1488
        // Write the SST block header information
-
 
1489
        $header      = pack("vv", $record, $length);
-
 
1490
        $data        = pack("VV", $this->_str_total, $this->_str_unique);
-
 
1491
        $this->_append($header . $data);
-
 
1492
 
-
 
1493
 
-
 
1494
 
-
 
1495
 
-
 
1496
        /* TODO: not good for performance */
-
 
1497
        foreach (array_keys($this->_str_table) as $string) {
-
 
1498
 
-
 
1499
            $string_length = strlen($string);
-
 
1500
            $headerinfo    = unpack("vlength/Cencoding", $string);
-
 
1501
            $encoding      = $headerinfo["encoding"];
-
 
1502
            $split_string  = 0;
-
 
1503
 
-
 
1504
            // Block length is the total length of the strings that will be
-
 
1505
            // written out in a single SST or CONTINUE block.
-
 
1506
            //
-
 
1507
            $block_length += $string_length;
-
 
1508
 
-
 
1509
 
-
 
1510
            // We can write the string if it doesn't cross a CONTINUE boundary
-
 
1511
            if ($block_length < $continue_limit) {
-
 
1512
                $this->_append($string);
-
 
1513
                $written += $string_length;
-
 
1514
                continue;
-
 
1515
            }
-
 
1516
 
-
 
1517
            // Deal with the cases where the next string to be written will exceed
-
 
1518
            // the CONTINUE boundary. If the string is very long it may need to be
-
 
1519
            // written in more than one CONTINUE record.
-
 
1520
            //
-
 
1521
            while ($block_length >= $continue_limit) {
-
 
1522
 
-
 
1523
                // We need to avoid the case where a string is continued in the first
-
 
1524
                // n bytes that contain the string header information.
-
 
1525
                //
-
 
1526
                $header_length   = 3; // Min string + header size -1
-
 
1527
                $space_remaining = $continue_limit - $written - $continue;
-
 
1528
 
-
 
1529
 
-
 
1530
                // Unicode data should only be split on char (2 byte) boundaries.
-
 
1531
                // Therefore, in some cases we need to reduce the amount of available
-
 
1532
                // space by 1 byte to ensure the correct alignment.
-
 
1533
                $align = 0;
-
 
1534
 
-
 
1535
                // Only applies to Unicode strings
-
 
1536
                if ($encoding == 1) {
-
 
1537
                    // Min string + header size -1
-
 
1538
                    $header_length = 4;
-
 
1539
 
-
 
1540
                    if ($space_remaining > $header_length) {
-
 
1541
                        // String contains 3 byte header => split on odd boundary
-
 
1542
                        if (!$split_string && $space_remaining % 2 != 1) {
-
 
1543
                            $space_remaining--;
-
 
1544
                            $align = 1;
-
 
1545
                        }
-
 
1546
                        // Split section without header => split on even boundary
-
 
1547
                        else if ($split_string && $space_remaining % 2 == 1) {
-
 
1548
                            $space_remaining--;
-
 
1549
                            $align = 1;
-
 
1550
                        }
-
 
1551
 
-
 
1552
                        $split_string = 1;
-
 
1553
                    }
-
 
1554
                }
-
 
1555
 
-
 
1556
 
-
 
1557
                if ($space_remaining > $header_length) {
-
 
1558
                    // Write as much as possible of the string in the current block
-
 
1559
                    $tmp = substr($string, 0, $space_remaining);
-
 
1560
                    $this->_append($tmp);
-
 
1561
 
-
 
1562
                    // The remainder will be written in the next block(s)
-
 
1563
                    $string = substr($string, $space_remaining);
-
 
1564
 
-
 
1565
                    // Reduce the current block length by the amount written
-
 
1566
                    $block_length -= $continue_limit - $continue - $align;
-
 
1567
 
-
 
1568
                    // If the current string was split then the next CONTINUE block
-
 
1569
                    // should have the string continue flag (grbit) set unless the
-
 
1570
                    // split string fits exactly into the remaining space.
-
 
1571
                    //
-
 
1572
                    if ($block_length > 0) {
-
 
1573
                        $continue = 1;
-
 
1574
                    } else {
-
 
1575
                        $continue = 0;
-
 
1576
                    }
-
 
1577
                } else {
-
 
1578
                    // Not enough space to start the string in the current block
-
 
1579
                    $block_length -= $continue_limit - $space_remaining - $continue;
-
 
1580
                    $continue = 0;
-
 
1581
                }
-
 
1582
 
-
 
1583
                // Write the CONTINUE block header
-
 
1584
                if (!empty($this->_block_sizes)) {
-
 
1585
                    $record  = 0x003C;
-
 
1586
                    $length  = array_shift($tmp_block_sizes);
-
 
1587
 
-
 
1588
                    $header  = pack('vv', $record, $length);
-
 
1589
                    if ($continue) {
-
 
1590
                        $header .= pack('C', $encoding);
-
 
1591
                    }
-
 
1592
                    $this->_append($header);
-
 
1593
                }
-
 
1594
 
-
 
1595
                // If the string (or substr) is small enough we can write it in the
-
 
1596
                // new CONTINUE block. Else, go through the loop again to write it in
-
 
1597
                // one or more CONTINUE blocks
-
 
1598
                //
-
 
1599
                if ($block_length < $continue_limit) {
-
 
1600
                    $this->_append($string);
-
 
1601
                    $written = $block_length;
-
 
1602
                } else {
-
 
1603
                    $written = 0;
-
 
1604
                }
-
 
1605
            }
-
 
1606
        }
-
 
1607
    }
-
 
1608
 
-
 
1609
 
1041
}
1610
}
1042
?>
1611