PageRenderTime 69ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/excel/Worksheet.php

https://bitbucket.org/ngmares/moodle
PHP | 2835 lines | 1483 code | 328 blank | 1024 comment | 183 complexity | 98619de941639d190972ffa46be392bf MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0, MPL-2.0-no-copyleft-exception, GPL-3.0, Apache-2.0, BSD-3-Clause
  1. <?php
  2. /*
  3. * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
  4. *
  5. * The majority of this is _NOT_ my code. I simply ported it from the
  6. * PERL Spreadsheet::WriteExcel module.
  7. *
  8. * The author of the Spreadsheet::WriteExcel module is John McNamara
  9. * <jmcnamara@cpan.org>
  10. *
  11. * I _DO_ maintain this code, and John McNamara has nothing to do with the
  12. * porting of this code to PHP. Any questions directly related to this
  13. * class library should be directed to me.
  14. *
  15. * License Information:
  16. *
  17. * Spreadsheet::WriteExcel: A library for generating Excel Spreadsheets
  18. * Copyright (C) 2002 Xavier Noguer xnoguer@rezebra.com
  19. *
  20. * This library is free software; you can redistribute it and/or
  21. * modify it under the terms of the GNU Lesser General Public
  22. * License as published by the Free Software Foundation; either
  23. * version 2.1 of the License, or (at your option) any later version.
  24. *
  25. * This library is distributed in the hope that it will be useful,
  26. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  27. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  28. * Lesser General Public License for more details.
  29. *
  30. * You should have received a copy of the GNU Lesser General Public
  31. * License along with this library; if not, write to the Free Software
  32. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  33. */
  34. require_once('Parser.php');
  35. require_once('BIFFwriter.php');
  36. /**
  37. * Class for generating Excel Spreadsheets
  38. *
  39. * @author Xavier Noguer <xnoguer@rezebra.com>
  40. * @package Spreadsheet_WriteExcel
  41. */
  42. class Worksheet extends BIFFwriter
  43. {
  44. /**
  45. * Constructor
  46. *
  47. * @param string $name The name of the new worksheet
  48. * @param integer $index The index of the new worksheet
  49. * @param mixed &$activesheet The current activesheet of the workbook we belong to
  50. * @param mixed &$firstsheet The first worksheet in the workbook we belong to
  51. * @param mixed &$url_format The default format for hyperlinks
  52. * @param mixed &$parser The formula parser created for the Workbook
  53. */
  54. function Worksheet($name,$index,&$activesheet,&$firstsheet,&$url_format,&$parser)
  55. {
  56. $this->BIFFwriter(); // It needs to call its parent's constructor explicitly
  57. $rowmax = 65536; // 16384 in Excel 5
  58. $colmax = 256;
  59. $strmax = 255;
  60. $this->name = $name;
  61. $this->index = $index;
  62. $this->activesheet = &$activesheet;
  63. $this->firstsheet = &$firstsheet;
  64. $this->_url_format = $url_format;
  65. $this->_parser = &$parser;
  66. $this->ext_sheets = array();
  67. $this->_using_tmpfile = 1;
  68. $this->_filehandle = "";
  69. $this->fileclosed = 0;
  70. $this->offset = 0;
  71. $this->xls_rowmax = $rowmax;
  72. $this->xls_colmax = $colmax;
  73. $this->xls_strmax = $strmax;
  74. $this->dim_rowmin = $rowmax +1;
  75. $this->dim_rowmax = 0;
  76. $this->dim_colmin = $colmax +1;
  77. $this->dim_colmax = 0;
  78. $this->colinfo = array();
  79. $this->_selection = array(0,0,0,0);
  80. $this->_panes = array();
  81. $this->_active_pane = 3;
  82. $this->_frozen = 0;
  83. $this->selected = 0;
  84. $this->_paper_size = 0x0;
  85. $this->_orientation = 0x1;
  86. $this->_header = '';
  87. $this->_footer = '';
  88. $this->_hcenter = 0;
  89. $this->_vcenter = 0;
  90. $this->_margin_head = 0.50;
  91. $this->_margin_foot = 0.50;
  92. $this->_margin_left = 0.75;
  93. $this->_margin_right = 0.75;
  94. $this->_margin_top = 1.00;
  95. $this->_margin_bottom = 1.00;
  96. $this->_title_rowmin = NULL;
  97. $this->_title_rowmax = NULL;
  98. $this->_title_colmin = NULL;
  99. $this->_title_colmax = NULL;
  100. $this->_print_rowmin = NULL;
  101. $this->_print_rowmax = NULL;
  102. $this->_print_colmin = NULL;
  103. $this->_print_colmax = NULL;
  104. $this->_print_gridlines = 1;
  105. $this->_print_headers = 0;
  106. $this->_fit_page = 0;
  107. $this->_fit_width = 0;
  108. $this->_fit_height = 0;
  109. $this->_hbreaks = array();
  110. $this->_vbreaks = array();
  111. $this->_protect = 0;
  112. $this->_password = NULL;
  113. $this->col_sizes = array();
  114. $this->row_sizes = array();
  115. $this->_zoom = 100;
  116. $this->_print_scale = 100;
  117. $this->_initialize();
  118. }
  119. /**
  120. * Open a tmp file to store the majority of the Worksheet data. If this fails,
  121. * for example due to write permissions, store the data in memory. This can be
  122. * slow for large files.
  123. */
  124. function _initialize()
  125. {
  126. // Open tmp file for storing Worksheet data
  127. $fh = tmpfile();
  128. if ( $fh) {
  129. // Store filehandle
  130. $this->_filehandle = $fh;
  131. }
  132. else {
  133. // If tmpfile() fails store data in memory
  134. $this->_using_tmpfile = 0;
  135. }
  136. }
  137. /**
  138. * Add data to the beginning of the workbook (note the reverse order)
  139. * and to the end of the workbook.
  140. *
  141. * @access public
  142. * @see Workbook::store_workbook()
  143. * @param array $sheetnames The array of sheetnames from the Workbook this
  144. * worksheet belongs to
  145. */
  146. function close($sheetnames)
  147. {
  148. $num_sheets = count($sheetnames);
  149. /***********************************************
  150. * Prepend in reverse order!!
  151. */
  152. // Prepend the sheet dimensions
  153. $this->_store_dimensions();
  154. // Prepend the sheet password
  155. $this->_store_password();
  156. // Prepend the sheet protection
  157. $this->_store_protect();
  158. // Prepend the page setup
  159. $this->_store_setup();
  160. // Prepend the bottom margin
  161. $this->_store_margin_bottom();
  162. // Prepend the top margin
  163. $this->_store_margin_top();
  164. // Prepend the right margin
  165. $this->_store_margin_right();
  166. // Prepend the left margin
  167. $this->_store_margin_left();
  168. // Prepend the page vertical centering
  169. $this->store_vcenter();
  170. // Prepend the page horizontal centering
  171. $this->store_hcenter();
  172. // Prepend the page footer
  173. $this->store_footer();
  174. // Prepend the page header
  175. $this->store_header();
  176. // Prepend the vertical page breaks
  177. $this->_store_vbreak();
  178. // Prepend the horizontal page breaks
  179. $this->_store_hbreak();
  180. // Prepend WSBOOL
  181. $this->_store_wsbool();
  182. // Prepend GRIDSET
  183. $this->_store_gridset();
  184. // Prepend PRINTGRIDLINES
  185. $this->_store_print_gridlines();
  186. // Prepend PRINTHEADERS
  187. $this->_store_print_headers();
  188. // Prepend EXTERNSHEET references
  189. for ($i = $num_sheets; $i > 0; $i--) {
  190. $sheetname = $sheetnames[$i-1];
  191. $this->_store_externsheet($sheetname);
  192. }
  193. // Prepend the EXTERNCOUNT of external references.
  194. $this->_store_externcount($num_sheets);
  195. // Prepend the COLINFO records if they exist
  196. if (!empty($this->colinfo)){
  197. for($i=0; $i < count($this->colinfo); $i++)
  198. {
  199. $this->_store_colinfo($this->colinfo[$i]);
  200. }
  201. $this->_store_defcol();
  202. }
  203. // Prepend the BOF record
  204. $this->_store_bof(0x0010);
  205. /*
  206. * End of prepend. Read upwards from here.
  207. ***********************************************/
  208. // Append
  209. $this->_store_window2();
  210. $this->_store_zoom();
  211. if(!empty($this->_panes))
  212. $this->_store_panes($this->_panes);
  213. $this->_store_selection($this->_selection);
  214. $this->_store_eof();
  215. }
  216. /**
  217. * Retrieve the worksheet name. This is usefull when creating worksheets
  218. * without a name.
  219. *
  220. * @access public
  221. * @return string The worksheet's name
  222. */
  223. function get_name()
  224. {
  225. return($this->name);
  226. }
  227. /**
  228. * Retrieves data from memory in one chunk, or from disk in $buffer
  229. * sized chunks.
  230. *
  231. * @return string The data
  232. */
  233. function get_data()
  234. {
  235. $buffer = 4096;
  236. // Return data stored in memory
  237. if (isset($this->_data)) {
  238. $tmp = $this->_data;
  239. unset($this->_data);
  240. $fh = $this->_filehandle;
  241. if ($this->_using_tmpfile) {
  242. fseek($fh, 0);
  243. }
  244. return($tmp);
  245. }
  246. // Return data stored on disk
  247. if ($this->_using_tmpfile) {
  248. if ($tmp = fread($this->_filehandle, $buffer)) {
  249. return($tmp);
  250. }
  251. }
  252. // No data to return
  253. return('');
  254. }
  255. /**
  256. * Set this worksheet as a selected worksheet, i.e. the worksheet has its tab
  257. * highlighted.
  258. *
  259. * @access public
  260. */
  261. function select()
  262. {
  263. $this->selected = 1;
  264. }
  265. /**
  266. * Set this worksheet as the active worksheet, i.e. the worksheet that is
  267. * displayed when the workbook is opened. Also set it as selected.
  268. *
  269. * @access public
  270. */
  271. function activate()
  272. {
  273. $this->selected = 1;
  274. $this->activesheet =& $this->index;
  275. }
  276. /**
  277. * Set this worksheet as the first visible sheet. This is necessary
  278. * when there are a large number of worksheets and the activated
  279. * worksheet is not visible on the screen.
  280. *
  281. * @access public
  282. */
  283. function set_first_sheet()
  284. {
  285. $this->firstsheet = $this->index;
  286. }
  287. /**
  288. * Set the worksheet protection flag to prevent accidental modification and to
  289. * hide formulas if the locked and hidden format properties have been set.
  290. *
  291. * @access public
  292. * @param string $password The password to use for protecting the sheet.
  293. */
  294. function protect($password)
  295. {
  296. $this->_protect = 1;
  297. $this->_password = $this->_encode_password($password);
  298. }
  299. /**
  300. * Set the width of a single column or a range of columns.
  301. *
  302. * @access public
  303. * @see _store_colinfo()
  304. * @param integer $firstcol first column on the range
  305. * @param integer $lastcol last column on the range
  306. * @param integer $width width to set
  307. * @param mixed $format The optional XF format to apply to the columns
  308. * @param integer $hidden The optional hidden atribute
  309. */
  310. function set_column($firstcol, $lastcol, $width, $format = 0, $hidden = 0)
  311. {
  312. $this->colinfo[] = array($firstcol, $lastcol, $width, $format, $hidden);
  313. // Set width to zero if column is hidden
  314. $width = ($hidden) ? 0 : $width;
  315. for($col = $firstcol; $col <= $lastcol; $col++) {
  316. $this->col_sizes[$col] = $width;
  317. }
  318. }
  319. /**
  320. * Set which cell or cells are selected in a worksheet
  321. *
  322. * @access public
  323. * @param integer $first_row first row in the selected quadrant
  324. * @param integer $first_column first column in the selected quadrant
  325. * @param integer $last_row last row in the selected quadrant
  326. * @param integer $last_column last column in the selected quadrant
  327. * @see _store_selection()
  328. */
  329. function set_selection($first_row,$first_column,$last_row,$last_column)
  330. {
  331. $this->_selection = array($first_row,$first_column,$last_row,$last_column);
  332. }
  333. /**
  334. * Set panes and mark them as frozen.
  335. *
  336. * @access public
  337. * @param array $panes This is the only parameter received and is composed of the following:
  338. * 0 => Vertical split position,
  339. * 1 => Horizontal split position
  340. * 2 => Top row visible
  341. * 3 => Leftmost column visible
  342. * 4 => Active pane
  343. */
  344. function freeze_panes($panes)
  345. {
  346. $this->_frozen = 1;
  347. $this->_panes = $panes;
  348. }
  349. /**
  350. * Set panes and mark them as unfrozen.
  351. *
  352. * @access public
  353. * @param array $panes This is the only parameter received and is composed of the following:
  354. * 0 => Vertical split position,
  355. * 1 => Horizontal split position
  356. * 2 => Top row visible
  357. * 3 => Leftmost column visible
  358. * 4 => Active pane
  359. */
  360. function thaw_panes($panes)
  361. {
  362. $this->_frozen = 0;
  363. $this->_panes = $panes;
  364. }
  365. /**
  366. * Set the page orientation as portrait.
  367. *
  368. * @access public
  369. */
  370. function set_portrait()
  371. {
  372. $this->_orientation = 1;
  373. }
  374. /**
  375. * Set the page orientation as landscape.
  376. *
  377. * @access public
  378. */
  379. function set_landscape()
  380. {
  381. $this->_orientation = 0;
  382. }
  383. /**
  384. * Set the paper type. Ex. 1 = US Letter, 9 = A4
  385. *
  386. * @access public
  387. * @param integer $size The type of paper size to use
  388. */
  389. function set_paper($size = 0)
  390. {
  391. $this->_paper_size = $size;
  392. }
  393. /**
  394. * Set the page header caption and optional margin.
  395. *
  396. * @access public
  397. * @param string $string The header text
  398. * @param float $margin optional head margin in inches.
  399. */
  400. function set_header($string,$margin = 0.50)
  401. {
  402. if (strlen($string) >= 255) {
  403. //carp 'Header string must be less than 255 characters';
  404. return;
  405. }
  406. $this->_header = $string;
  407. $this->_margin_head = $margin;
  408. }
  409. /**
  410. * Set the page footer caption and optional margin.
  411. *
  412. * @access public
  413. * @param string $string The footer text
  414. * @param float $margin optional foot margin in inches.
  415. */
  416. function set_footer($string,$margin = 0.50)
  417. {
  418. if (strlen($string) >= 255) {
  419. //carp 'Footer string must be less than 255 characters';
  420. return;
  421. }
  422. $this->_footer = $string;
  423. $this->_margin_foot = $margin;
  424. }
  425. /**
  426. * Center the page horinzontally.
  427. *
  428. * @access public
  429. * @param integer $center the optional value for centering. Defaults to 1 (center).
  430. */
  431. function center_horizontally($center = 1)
  432. {
  433. $this->_hcenter = $center;
  434. }
  435. /**
  436. * Center the page horinzontally.
  437. *
  438. * @access public
  439. * @param integer $center the optional value for centering. Defaults to 1 (center).
  440. */
  441. function center_vertically($center = 1)
  442. {
  443. $this->_vcenter = $center;
  444. }
  445. /**
  446. * Set all the page margins to the same value in inches.
  447. *
  448. * @access public
  449. * @param float $margin The margin to set in inches
  450. */
  451. function set_margins($margin)
  452. {
  453. $this->set_margin_left($margin);
  454. $this->set_margin_right($margin);
  455. $this->set_margin_top($margin);
  456. $this->set_margin_bottom($margin);
  457. }
  458. /**
  459. * Set the left and right margins to the same value in inches.
  460. *
  461. * @access public
  462. * @param float $margin The margin to set in inches
  463. */
  464. function set_margins_LR($margin)
  465. {
  466. $this->set_margin_left($margin);
  467. $this->set_margin_right($margin);
  468. }
  469. /**
  470. * Set the top and bottom margins to the same value in inches.
  471. *
  472. * @access public
  473. * @param float $margin The margin to set in inches
  474. */
  475. function set_margins_TB($margin)
  476. {
  477. $this->set_margin_top($margin);
  478. $this->set_margin_bottom($margin);
  479. }
  480. /**
  481. * Set the left margin in inches.
  482. *
  483. * @access public
  484. * @param float $margin The margin to set in inches
  485. */
  486. function set_margin_left($margin = 0.75)
  487. {
  488. $this->_margin_left = $margin;
  489. }
  490. /**
  491. * Set the right margin in inches.
  492. *
  493. * @access public
  494. * @param float $margin The margin to set in inches
  495. */
  496. function set_margin_right($margin = 0.75)
  497. {
  498. $this->_margin_right = $margin;
  499. }
  500. /**
  501. * Set the top margin in inches.
  502. *
  503. * @access public
  504. * @param float $margin The margin to set in inches
  505. */
  506. function set_margin_top($margin = 1.00)
  507. {
  508. $this->_margin_top = $margin;
  509. }
  510. /**
  511. * Set the bottom margin in inches.
  512. *
  513. * @access public
  514. * @param float $margin The margin to set in inches
  515. */
  516. function set_margin_bottom($margin = 1.00)
  517. {
  518. $this->_margin_bottom = $margin;
  519. }
  520. /**
  521. * Set the rows to repeat at the top of each printed page. See also the
  522. * _store_name_xxxx() methods in Workbook.php
  523. *
  524. * @access public
  525. * @param integer $first_row First row to repeat
  526. * @param integer $last_row Last row to repeat. Optional.
  527. */
  528. function repeat_rows($first_row, $last_row = NULL)
  529. {
  530. $this->_title_rowmin = $first_row;
  531. if(isset($last_row)) { //Second row is optional
  532. $this->_title_rowmax = $last_row;
  533. }
  534. else {
  535. $this->_title_rowmax = $first_row;
  536. }
  537. }
  538. /**
  539. * Set the columns to repeat at the left hand side of each printed page.
  540. * See also the _store_names() methods in Workbook.php
  541. *
  542. * @access public
  543. * @param integer $first_col First column to repeat
  544. * @param integer $last_col Last column to repeat. Optional.
  545. */
  546. function repeat_columns($first_col, $last_col = NULL)
  547. {
  548. $this->_title_colmin = $first_col;
  549. if(isset($last_col)) { // Second col is optional
  550. $this->_title_colmax = $last_col;
  551. }
  552. else {
  553. $this->_title_colmax = $first_col;
  554. }
  555. }
  556. /**
  557. * Set the area of each worksheet that will be printed.
  558. *
  559. * @access public
  560. * @see Workbook::_store_names()
  561. * @param integer $first_row First row of the area to print
  562. * @param integer $first_col First column of the area to print
  563. * @param integer $last_row Last row of the area to print
  564. * @param integer $last_col Last column of the area to print
  565. */
  566. function print_area($first_row, $first_col, $last_row, $last_col)
  567. {
  568. $this->_print_rowmin = $first_row;
  569. $this->_print_colmin = $first_col;
  570. $this->_print_rowmax = $last_row;
  571. $this->_print_colmax = $last_col;
  572. }
  573. /**
  574. * Set the option to hide gridlines on the printed page.
  575. *
  576. * @access public
  577. * @see _store_print_gridlines(), _store_gridset()
  578. */
  579. function hide_gridlines()
  580. {
  581. $this->_print_gridlines = 0;
  582. }
  583. /**
  584. * Set the option to print the row and column headers on the printed page.
  585. * See also the _store_print_headers() method below.
  586. *
  587. * @access public
  588. * @see _store_print_headers()
  589. * @param integer $print Whether to print the headers or not. Defaults to 1 (print).
  590. */
  591. function print_row_col_headers($print = 1)
  592. {
  593. $this->_print_headers = $print;
  594. }
  595. /**
  596. * Store the vertical and horizontal number of pages that will define the
  597. * maximum area printed. It doesn't seem to work with OpenOffice.
  598. *
  599. * @access public
  600. * @param integer $width Maximun width of printed area in pages
  601. * @param integer $heigth Maximun heigth of printed area in pages
  602. * @see set_print_scale()
  603. */
  604. function fit_to_pages($width, $height)
  605. {
  606. $this->_fit_page = 1;
  607. $this->_fit_width = $width;
  608. $this->_fit_height = $height;
  609. }
  610. /**
  611. * Store the horizontal page breaks on a worksheet (for printing).
  612. * The breaks represent the row after which the break is inserted.
  613. *
  614. * @access public
  615. * @param array $breaks Array containing the horizontal page breaks
  616. */
  617. function set_h_pagebreaks($breaks)
  618. {
  619. foreach($breaks as $break) {
  620. array_push($this->_hbreaks,$break);
  621. }
  622. }
  623. /**
  624. * Store the vertical page breaks on a worksheet (for printing).
  625. * The breaks represent the column after which the break is inserted.
  626. *
  627. * @access public
  628. * @param array $breaks Array containing the vertical page breaks
  629. */
  630. function set_v_pagebreaks($breaks)
  631. {
  632. foreach($breaks as $break) {
  633. array_push($this->_vbreaks,$break);
  634. }
  635. }
  636. /**
  637. * Set the worksheet zoom factor.
  638. *
  639. * @access public
  640. * @param integer $scale The zoom factor
  641. */
  642. function set_zoom($scale = 100)
  643. {
  644. // Confine the scale to Excel's range
  645. if ($scale < 10 or $scale > 400) {
  646. //carp "Zoom factor $scale outside range: 10 <= zoom <= 400";
  647. $scale = 100;
  648. }
  649. $this->_zoom = floor($scale);
  650. }
  651. /**
  652. * Set the scale factor for the printed page.
  653. * It turns off the "fit to page" option
  654. *
  655. * @access public
  656. * @param integer $scale The optional scale factor. Defaults to 100
  657. */
  658. function set_print_scale($scale = 100)
  659. {
  660. // Confine the scale to Excel's range
  661. if ($scale < 10 or $scale > 400)
  662. {
  663. // REPLACE THIS FOR A WARNING
  664. die("Print scale $scale outside range: 10 <= zoom <= 400");
  665. $scale = 100;
  666. }
  667. // Turn off "fit to page" option
  668. $this->_fit_page = 0;
  669. $this->_print_scale = floor($scale);
  670. }
  671. /**
  672. * Map to the appropriate write method acording to the token recieved.
  673. *
  674. * @access public
  675. * @param integer $row The row of the cell we are writing to
  676. * @param integer $col The column of the cell we are writing to
  677. * @param mixed $token What we are writing
  678. * @param mixed $format The optional format to apply to the cell
  679. */
  680. function write($row, $col, $token, $format = 0)
  681. {
  682. // Check for a cell reference in A1 notation and substitute row and column
  683. /*if ($_[0] =~ /^\D/) {
  684. @_ = $this->_substitute_cellref(@_);
  685. }*/
  686. /*
  687. # Match an array ref.
  688. if (ref $token eq "ARRAY") {
  689. return $this->write_row(@_);
  690. }*/
  691. // Match number
  692. if (preg_match("/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/",$token)) {
  693. return $this->write_number($row,$col,$token,$format);
  694. }
  695. // Match http or ftp URL
  696. elseif (preg_match("/^[fh]tt?p:\/\//",$token)) {
  697. return $this->write_url($row, $col, $token, $format);
  698. }
  699. // Match mailto:
  700. elseif (preg_match("/^mailto:/",$token)) {
  701. return $this->write_url($row, $col, $token, $format);
  702. }
  703. // Match internal or external sheet link
  704. elseif (preg_match("/^(?:in|ex)ternal:/",$token)) {
  705. return $this->write_url($row, $col, $token, $format);
  706. }
  707. // Match formula
  708. elseif (preg_match("/^=/",$token)) {
  709. return $this->write_formula($row, $col, $token, $format);
  710. }
  711. // Match formula
  712. elseif (preg_match("/^@/",$token)) {
  713. return $this->write_formula($row, $col, $token, $format);
  714. }
  715. // Match blank
  716. elseif ($token == '') {
  717. return $this->write_blank($row,$col,$format);
  718. }
  719. // Default: match string
  720. else {
  721. return $this->write_string($row,$col,$token,$format);
  722. }
  723. }
  724. /**
  725. * Returns an index to the XF record in the workbook
  726. *
  727. * @param mixed $format The optional XF format
  728. * @return integer The XF record index
  729. */
  730. function _XF(&$format)
  731. {
  732. if($format != 0)
  733. {
  734. return($format->get_xf_index());
  735. }
  736. else
  737. {
  738. return(0x0F);
  739. }
  740. }
  741. /******************************************************************************
  742. *******************************************************************************
  743. *
  744. * Internal methods
  745. */
  746. /**
  747. * Store Worksheet data in memory using the parent's class append() or to a
  748. * temporary file, the default.
  749. *
  750. * @param string $data The binary data to append
  751. */
  752. function _append($data)
  753. {
  754. if ($this->_using_tmpfile)
  755. {
  756. // Add CONTINUE records if necessary
  757. if (strlen($data) > $this->_limit) {
  758. $data = $this->_add_continue($data);
  759. }
  760. fwrite($this->_filehandle,$data);
  761. $this->_datasize += strlen($data);
  762. }
  763. else {
  764. parent::_append($data);
  765. }
  766. }
  767. /**
  768. * Substitute an Excel cell reference in A1 notation for zero based row and
  769. * column values in an argument list.
  770. *
  771. * Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
  772. *
  773. * @param string $cell The cell reference. Or range of cells.
  774. * @return array
  775. */
  776. function _substitute_cellref($cell)
  777. {
  778. $cell = strtoupper($cell);
  779. // Convert a column range: 'A:A' or 'B:G'
  780. if (preg_match("/([A-I]?[A-Z]):([A-I]?[A-Z])/",$cell,$match)) {
  781. list($no_use, $col1) = $this->_cell_to_rowcol($match[1] .'1'); // Add a dummy row
  782. list($no_use, $col2) = $this->_cell_to_rowcol($match[2] .'1'); // Add a dummy row
  783. return(array($col1, $col2));
  784. }
  785. // Convert a cell range: 'A1:B7'
  786. if (preg_match("/\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)/",$cell,$match)) {
  787. list($row1, $col1) = $this->_cell_to_rowcol($match[1]);
  788. list($row2, $col2) = $this->_cell_to_rowcol($match[2]);
  789. return(array($row1, $col1, $row2, $col2));
  790. }
  791. // Convert a cell reference: 'A1' or 'AD2000'
  792. if (preg_match("/\$?([A-I]?[A-Z]\$?\d+)/",$cell)) {
  793. list($row1, $col1) = $this->_cell_to_rowcol($match[1]);
  794. return(array($row1, $col1));
  795. }
  796. die("Unknown cell reference $cell ");
  797. }
  798. /**
  799. * Convert an Excel cell reference in A1 notation to a zero based row and column
  800. * reference; converts C1 to (0, 2).
  801. *
  802. * @param string $cell The cell reference.
  803. * @return array containing (row, column)
  804. */
  805. function _cell_to_rowcol($cell)
  806. {
  807. preg_match("/\$?([A-I]?[A-Z])\$?(\d+)/",$cell,$match);
  808. $col = $match[1];
  809. $row = $match[2];
  810. // Convert base26 column string to number
  811. $chars = str_split($col);
  812. $expn = 0;
  813. $col = 0;
  814. while ($chars) {
  815. $char = array_pop($chars); // LS char first
  816. $col += (ord($char) -ord('A') +1) * pow(26,$expn);
  817. $expn++;
  818. }
  819. // Convert 1-index to zero-index
  820. $row--;
  821. $col--;
  822. return(array($row, $col));
  823. }
  824. /**
  825. * Based on the algorithm provided by Daniel Rentz of OpenOffice.
  826. *
  827. * @param string $plaintext The password to be encoded in plaintext.
  828. * @return string The encoded password
  829. */
  830. function _encode_password($plaintext)
  831. {
  832. $password = 0x0000;
  833. $i = 1; // char position
  834. // split the plain text password in its component characters
  835. $chars = preg_split('//', $plaintext, -1, PREG_SPLIT_NO_EMPTY);
  836. foreach($chars as $char)
  837. {
  838. $value = ord($char) << $i; // shifted ASCII value
  839. $bit_16 = $value & 0x8000; // the bit 16
  840. $bit_16 >>= 15; // 0x0000 or 0x0001
  841. //$bit_17 = $value & 0x00010000;
  842. //$bit_17 >>= 15;
  843. $value &= 0x7fff; // first 15 bits
  844. $password ^= ($value | $bit_16);
  845. //$password ^= ($value | $bit_16 | $bit_17);
  846. $i++;
  847. }
  848. $password ^= strlen($plaintext);
  849. $password ^= 0xCE4B;
  850. return($password);
  851. }
  852. /******************************************************************************
  853. *******************************************************************************
  854. *
  855. * BIFF RECORDS
  856. */
  857. /**
  858. * Write a double to the specified row and column (zero indexed).
  859. * An integer can be written as a double. Excel will display an
  860. * integer. $format is optional.
  861. *
  862. * Returns 0 : normal termination
  863. * -2 : row or column out of range
  864. *
  865. * @access public
  866. * @param integer $row Zero indexed row
  867. * @param integer $col Zero indexed column
  868. * @param float $num The number to write
  869. * @param mixed $format The optional XF format
  870. */
  871. function write_number($row, $col, $num, $format = 0)
  872. {
  873. $record = 0x0203; // Record identifier
  874. $length = 0x000E; // Number of bytes to follow
  875. $xf = $this->_XF($format); // The cell format
  876. // Check that row and col are valid and store max and min values
  877. if ($row >= $this->xls_rowmax)
  878. {
  879. return(-2);
  880. }
  881. if ($col >= $this->xls_colmax)
  882. {
  883. return(-2);
  884. }
  885. if ($row < $this->dim_rowmin)
  886. {
  887. $this->dim_rowmin = $row;
  888. }
  889. if ($row > $this->dim_rowmax)
  890. {
  891. $this->dim_rowmax = $row;
  892. }
  893. if ($col < $this->dim_colmin)
  894. {
  895. $this->dim_colmin = $col;
  896. }
  897. if ($col > $this->dim_colmax)
  898. {
  899. $this->dim_colmax = $col;
  900. }
  901. $header = pack("vv", $record, $length);
  902. $data = pack("vvv", $row, $col, $xf);
  903. $xl_double = pack("d", $num);
  904. if ($this->_byte_order) // if it's Big Endian
  905. {
  906. $xl_double = strrev($xl_double);
  907. }
  908. $this->_append($header.$data.$xl_double);
  909. return(0);
  910. }
  911. /**
  912. * Write a string to the specified row and column (zero indexed).
  913. * NOTE: there is an Excel 5 defined limit of 255 characters.
  914. * $format is optional.
  915. * Returns 0 : normal termination
  916. * -1 : insufficient number of arguments
  917. * -2 : row or column out of range
  918. * -3 : long string truncated to 255 chars
  919. *
  920. * @access public
  921. * @param integer $row Zero indexed row
  922. * @param integer $col Zero indexed column
  923. * @param string $str The string to write
  924. * @param mixed $format The XF format for the cell
  925. */
  926. function write_string($row, $col, $str, $format = 0)
  927. {
  928. $strlen = strlen($str);
  929. $record = 0x0204; // Record identifier
  930. $length = 0x0008 + $strlen; // Bytes to follow
  931. $xf = $this->_XF($format); // The cell format
  932. $str_error = 0;
  933. // Check that row and col are valid and store max and min values
  934. if ($row >= $this->xls_rowmax)
  935. {
  936. return(-2);
  937. }
  938. if ($col >= $this->xls_colmax)
  939. {
  940. return(-2);
  941. }
  942. if ($row < $this->dim_rowmin)
  943. {
  944. $this->dim_rowmin = $row;
  945. }
  946. if ($row > $this->dim_rowmax)
  947. {
  948. $this->dim_rowmax = $row;
  949. }
  950. if ($col < $this->dim_colmin)
  951. {
  952. $this->dim_colmin = $col;
  953. }
  954. if ($col > $this->dim_colmax)
  955. {
  956. $this->dim_colmax = $col;
  957. }
  958. if ($strlen > $this->xls_strmax) // LABEL must be < 255 chars
  959. {
  960. $str = substr($str, 0, $this->xls_strmax);
  961. $length = 0x0008 + $this->xls_strmax;
  962. $strlen = $this->xls_strmax;
  963. $str_error = -3;
  964. }
  965. $header = pack("vv", $record, $length);
  966. $data = pack("vvvv", $row, $col, $xf, $strlen);
  967. $this->_append($header.$data.$str);
  968. return($str_error);
  969. }
  970. /**
  971. * Writes a note associated with the cell given by the row and column.
  972. * NOTE records don't have a length limit.
  973. *
  974. * @access public
  975. * @param integer $row Zero indexed row
  976. * @param integer $col Zero indexed column
  977. * @param string $note The note to write
  978. */
  979. function write_note($row, $col, $note)
  980. {
  981. $note_length = strlen($note);
  982. $record = 0x001C; // Record identifier
  983. $max_length = 2048; // Maximun length for a NOTE record
  984. //$length = 0x0006 + $note_length; // Bytes to follow
  985. // Check that row and col are valid and store max and min values
  986. if ($row >= $this->xls_rowmax)
  987. {
  988. return(-2);
  989. }
  990. if ($col >= $this->xls_colmax)
  991. {
  992. return(-2);
  993. }
  994. if ($row < $this->dim_rowmin)
  995. {
  996. $this->dim_rowmin = $row;
  997. }
  998. if ($row > $this->dim_rowmax)
  999. {
  1000. $this->dim_rowmax = $row;
  1001. }
  1002. if ($col < $this->dim_colmin)
  1003. {
  1004. $this->dim_colmin = $col;
  1005. }
  1006. if ($col > $this->dim_colmax)
  1007. {
  1008. $this->dim_colmax = $col;
  1009. }
  1010. // Length for this record is no more than 2048 + 6
  1011. $length = 0x0006 + min($note_length, 2048);
  1012. $header = pack("vv", $record, $length);
  1013. $data = pack("vvv", $row, $col, $note_length);
  1014. $this->_append($header.$data.substr($note, 0, 2048));
  1015. for($i = $max_length; $i < $note_length; $i += $max_length)
  1016. {
  1017. $chunk = substr($note, $i, $max_length);
  1018. $length = 0x0006 + strlen($chunk);
  1019. $header = pack("vv", $record, $length);
  1020. $data = pack("vvv", -1, 0, strlen($chunk));
  1021. $this->_append($header.$data.$chunk);
  1022. }
  1023. return(0);
  1024. }
  1025. /**
  1026. * Write a blank cell to the specified row and column (zero indexed).
  1027. * A blank cell is used to specify formatting without adding a string
  1028. * or a number.
  1029. *
  1030. * A blank cell without a format serves no purpose. Therefore, we don't write
  1031. * a BLANK record unless a format is specified. This is mainly an optimisation
  1032. * for the write_row() and write_col() methods.
  1033. *
  1034. * Returns 0 : normal termination (including no format)
  1035. * -1 : insufficient number of arguments
  1036. * -2 : row or column out of range
  1037. *
  1038. * @access public
  1039. * @param integer $row Zero indexed row
  1040. * @param integer $col Zero indexed column
  1041. * @param mixed $format The XF format
  1042. */
  1043. function write_blank($row, $col, $format = 0)
  1044. {
  1045. // Don't write a blank cell unless it has a format
  1046. if ($format == 0)
  1047. {
  1048. return(0);
  1049. }
  1050. $record = 0x0201; // Record identifier
  1051. $length = 0x0006; // Number of bytes to follow
  1052. $xf = $this->_XF($format); // The cell format
  1053. // Check that row and col are valid and store max and min values
  1054. if ($row >= $this->xls_rowmax)
  1055. {
  1056. return(-2);
  1057. }
  1058. if ($col >= $this->xls_colmax)
  1059. {
  1060. return(-2);
  1061. }
  1062. if ($row < $this->dim_rowmin)
  1063. {
  1064. $this->dim_rowmin = $row;
  1065. }
  1066. if ($row > $this->dim_rowmax)
  1067. {
  1068. $this->dim_rowmax = $row;
  1069. }
  1070. if ($col < $this->dim_colmin)
  1071. {
  1072. $this->dim_colmin = $col;
  1073. }
  1074. if ($col > $this->dim_colmax)
  1075. {
  1076. $this->dim_colmax = $col;
  1077. }
  1078. $header = pack("vv", $record, $length);
  1079. $data = pack("vvv", $row, $col, $xf);
  1080. $this->_append($header.$data);
  1081. return 0;
  1082. }
  1083. /**
  1084. * Write a formula to the specified row and column (zero indexed).
  1085. * The textual representation of the formula is passed to the parser in
  1086. * Parser.php which returns a packed binary string.
  1087. *
  1088. * Returns 0 : normal termination
  1089. * -2 : row or column out of range
  1090. *
  1091. * @access public
  1092. * @param integer $row Zero indexed row
  1093. * @param integer $col Zero indexed column
  1094. * @param string $formula The formula text string
  1095. * @param mixed $format The optional XF format
  1096. */
  1097. function write_formula($row, $col, $formula, $format = 0)
  1098. {
  1099. $record = 0x0006; // Record identifier
  1100. // Excel normally stores the last calculated value of the formula in $num.
  1101. // Clearly we are not in a position to calculate this a priori. Instead
  1102. // we set $num to zero and set the option flags in $grbit to ensure
  1103. // automatic calculation of the formula when the file is opened.
  1104. //
  1105. $xf = $this->_XF($format); // The cell format
  1106. $num = 0x00; // Current value of formula
  1107. $grbit = 0x03; // Option flags
  1108. $chn = 0x0000; // Must be zero
  1109. // Check that row and col are valid and store max and min values
  1110. if ($row >= $this->xls_rowmax)
  1111. {
  1112. return(-2);
  1113. }
  1114. if ($col >= $this->xls_colmax)
  1115. {
  1116. return(-2);
  1117. }
  1118. if ($row < $this->dim_rowmin)
  1119. {
  1120. $this->dim_rowmin = $row;
  1121. }
  1122. if ($row > $this->dim_rowmax)
  1123. {
  1124. $this->dim_rowmax = $row;
  1125. }
  1126. if ($col < $this->dim_colmin)
  1127. {
  1128. $this->dim_colmin = $col;
  1129. }
  1130. if ($col > $this->dim_colmax)
  1131. {
  1132. $this->dim_colmax = $col;
  1133. }
  1134. // Strip the '=' or '@' sign at the beginning of the formula string
  1135. if (preg_match("/^=/",$formula)) {
  1136. $formula = preg_replace("/(^=)/","",$formula);
  1137. }
  1138. elseif(preg_match("/^@/",$formula)) {
  1139. $formula = preg_replace("/(^@)/","",$formula);
  1140. }
  1141. else {
  1142. die("Unrecognised character for formula");
  1143. }
  1144. // Parse the formula using the parser in Parser.php
  1145. //$tree = new Parser($this->_byte_order);
  1146. $this->_parser->parse($formula);
  1147. //$tree->parse($formula);
  1148. $formula = $this->_parser->to_reverse_polish();
  1149. $formlen = strlen($formula); // Length of the binary string
  1150. $length = 0x16 + $formlen; // Length of the record data
  1151. $header = pack("vv", $record, $length);
  1152. $data = pack("vvvdvVv", $row, $col, $xf, $num,
  1153. $grbit, $chn, $formlen);
  1154. $this->_append($header.$data.$formula);
  1155. return 0;
  1156. }
  1157. /**
  1158. * Write a hyperlink. This is comprised of two elements: the visible label and
  1159. * the invisible link. The visible label is the same as the link unless an
  1160. * alternative string is specified. The label is written using the
  1161. * write_string() method. Therefore the 255 characters string limit applies.
  1162. * $string and $format are optional and their order is interchangeable.
  1163. *
  1164. * The hyperlink can be to a http, ftp, mail, internal sheet, or external
  1165. * directory url.
  1166. *
  1167. * Returns 0 : normal termination
  1168. * -1 : insufficient number of arguments
  1169. * -2 : row or column out of range
  1170. * -3 : long string truncated to 255 chars
  1171. *
  1172. * @access public
  1173. * @param integer $row Row
  1174. * @param integer $col Column
  1175. * @param string $url URL string
  1176. * @param string $string Alternative label
  1177. * @param mixed $format The cell format
  1178. */
  1179. function write_url($row, $col, $url, $string = '', $format = 0)
  1180. {
  1181. // Add start row and col to arg list
  1182. return($this->_write_url_range($row, $col, $row, $col, $url, $string, $format));
  1183. }
  1184. /**
  1185. * This is the more general form of write_url(). It allows a hyperlink to be
  1186. * written to a range of cells. This function also decides the type of hyperlink
  1187. * to be written. These are either, Web (http, ftp, mailto), Internal
  1188. * (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').
  1189. *
  1190. * See also write_url() above for a general description and return values.
  1191. *
  1192. * @param integer $row1 Start row
  1193. * @param integer $col1 Start column
  1194. * @param integer $row2 End row
  1195. * @param integer $col2 End column
  1196. * @param string $url URL string
  1197. * @param string $string Alternative label
  1198. * @param mixed $format The cell format
  1199. */
  1200. function _write_url_range($row1, $col1, $row2, $col2, $url, $string = '', $format = 0)
  1201. {
  1202. // Check for internal/external sheet links or default to web link
  1203. if (preg_match('[^internal:]', $url)) {
  1204. return($this->_write_url_internal($row1, $col1, $row2, $col2, $url, $string, $format));
  1205. }
  1206. if (preg_match('[^external:]', $url)) {
  1207. return($this->_write_url_external($row1, $col1, $row2, $col2, $url, $string, $format));
  1208. }
  1209. return($this->_write_url_web($row1, $col1, $row2, $col2, $url, $string, $format));
  1210. }
  1211. /**
  1212. * Used to write http, ftp and mailto hyperlinks.
  1213. * The link type ($options) is 0x03 is the same as absolute dir ref without
  1214. * sheet. However it is differentiated by the $unknown2 data stream.
  1215. *
  1216. * @see write_url()
  1217. * @param integer $row1 Start row
  1218. * @param integer $col1 Start column
  1219. * @param integer $row2 End row
  1220. * @param integer $col2 End column
  1221. * @param string $url URL string
  1222. * @param string $str Alternative label
  1223. * @param mixed $format The cell format
  1224. */
  1225. function _write_url_web($row1, $col1, $row2, $col2, $url, $str, $format = 0)
  1226. {
  1227. $record = 0x01B8; // Record identifier
  1228. $length = 0x00000; // Bytes to follow
  1229. if($format == 0) {
  1230. $format = $this->_url_format;
  1231. }
  1232. // Write the visible label using the write_string() method.
  1233. if($str == '') {
  1234. $str = $url;
  1235. }
  1236. $str_error = $this->write_string($row1, $col1, $str, $format);
  1237. if ($str_error == -2) {
  1238. return($str_error);
  1239. }
  1240. // Pack the undocumented parts of the hyperlink stream
  1241. $unknown1 = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
  1242. $unknown2 = pack("H*", "E0C9EA79F9BACE118C8200AA004BA90B");
  1243. // Pack the option flags
  1244. $options = pack("V", 0x03);
  1245. // Convert URL to a null terminated wchar string
  1246. $url = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
  1247. $url = $url . "\0\0\0";
  1248. // Pack the length of the URL
  1249. $url_len = pack("V", strlen($url));
  1250. // Calculate the data length
  1251. $length = 0x34 + strlen($url);
  1252. // Pack the header data
  1253. $header = pack("vv", $record, $length);
  1254. $data = pack("vvvv", $row1, $row2, $col1, $col2);
  1255. // Write the packed data
  1256. $this->_append( $header. $data.
  1257. $unknown1. $options.
  1258. $unknown2. $url_len. $url);
  1259. return($str_error);
  1260. }
  1261. /**
  1262. * Used to write internal reference hyperlinks such as "Sheet1!A1".
  1263. *
  1264. * @see write_url()
  1265. * @param integer $row1 Start row
  1266. * @param integer $col1 Start column
  1267. * @param integer $row2 End row
  1268. * @param integer $col2 End column
  1269. * @param string $url URL string
  1270. * @param string $str Alternative label
  1271. * @param mixed $format The cell format
  1272. */
  1273. function _write_url_internal($row1, $col1, $row2, $col2, $url, $str, $format = 0)
  1274. {
  1275. $record = 0x01B8; // Record identifier
  1276. $length = 0x00000; // Bytes to follow
  1277. if ($format == 0) {
  1278. $format = $this->_url_format;
  1279. }
  1280. // Strip URL type
  1281. $url = preg_replace('s[^internal:]', '', $url);
  1282. // Write the visible label
  1283. if($str == '') {
  1284. $str = $url;
  1285. }
  1286. $str_error = $this->write_string($row1, $col1, $str, $format);
  1287. if ($str_error == -2) {
  1288. return($str_error);
  1289. }
  1290. // Pack the undocumented parts of the hyperlink stream
  1291. $unknown1 = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
  1292. // Pack the option flags
  1293. $options = pack("V", 0x08);
  1294. // Convert the URL type and to a null terminated wchar string
  1295. $url = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
  1296. $url = $url . "\0\0\0";
  1297. // Pack the length of the URL as chars (not wchars)
  1298. $url_len = pack("V", floor(strlen($url)/2));
  1299. // Calculate the data length
  1300. $length = 0x24 + strlen($url);
  1301. // Pack the header data
  1302. $header = pack("vv", $record, $length);
  1303. $data = pack("vvvv", $row1, $row2, $col1, $col2);
  1304. // Write the packed data
  1305. $this->_append($header. $data.
  1306. $unknown1. $options.
  1307. $url_len. $url);
  1308. return($str_error);
  1309. }
  1310. /**
  1311. * Write links to external directory names such as 'c:\foo.xls',
  1312. * c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
  1313. *
  1314. * Note: Excel writes some relative links with the $dir_long string. We ignore
  1315. * these cases for the sake of simpler code.
  1316. *
  1317. * @see write_url()
  1318. * @param integer $row1 Start row
  1319. * @param integer $col1 Start column
  1320. * @param integer $row2 End row
  1321. * @param integer $col2 End column
  1322. * @param string $url URL string
  1323. * @param string $str Alternative label
  1324. * @param mixed $format The cell format
  1325. */
  1326. function _write_url_external($row1, $col1, $row2, $col2, $url, $str, $format = 0)
  1327. {
  1328. // Network drives are different. We will handle them separately
  1329. // MS/Novell network drives and shares start with \\
  1330. if (preg_match('[^external:\\\\]', $url)) {
  1331. return($this->_write_url_external_net($row1, $col1, $row2, $col2, $url, $str, $format));
  1332. }
  1333. $record = 0x01B8; // Record identifier
  1334. $length = 0x00000; // Bytes to follow
  1335. if ($format == 0) {
  1336. $format = $this->_url_format;
  1337. }
  1338. // Strip URL type and change Unix dir separator to Dos style (if needed)
  1339. //
  1340. $url = preg_replace('[^external:]', '', $url);
  1341. $url = preg_replace('[/]', "\\", $url);
  1342. // Write the visible label
  1343. if ($str == '') {
  1344. $str = preg_replace('[\#]', ' - ', $url);
  1345. }
  1346. $str_error = $this->write_string($row1, $col1, $str, $format);
  1347. if ($str_error == -2) {
  1348. return($str_error);
  1349. }
  1350. // Determine if the link is relative or absolute:
  1351. // relative if link contains no dir separator, "somefile.xls"
  1352. // relative if link starts with up-dir, "..\..\somefile.xls"
  1353. // otherwise, absolute
  1354. $absolute = 0x02; // Bit mask
  1355. if (!preg_match('[\\]', $url)) {
  1356. $absolute = 0x00;
  1357. }
  1358. if (preg_match('[^\.\.\\]', $url)) {
  1359. $absolute = 0x00;
  1360. }
  1361. // Determine if the link contains a sheet reference and change some of the
  1362. // parameters accordingly.
  1363. // Split the dir name and sheet name (if it exists)
  1364. list($dir_long , $sheet) = explode('/#/', $url);
  1365. $link_type = 0x01 | $absolute;
  1366. if (isset($sheet)) {
  1367. $link_type |= 0x08;
  1368. $sheet_len = pack("V", strlen($sheet) + 0x01);
  1369. $sheet = join("\0", str_split($sheet));
  1370. $sheet .= "\0\0\0";
  1371. }
  1372. else {
  1373. $sheet_len = '';
  1374. $sheet = '';
  1375. }
  1376. // Pack the link type
  1377. $link_type = pack("V", $link_type);
  1378. // Calculate the up-level dir count e.g.. (..\..\..\ == 3)
  1379. $up_count = preg_match_all("/\.\.\\/", $dir_long, $useless);
  1380. $up_count = pack("v", $up_count);
  1381. // Store the short dos dir name (null terminated)
  1382. $dir_short = preg_replace('/\.\.\\/', '', $dir_long) . "\0";
  1383. // Store the long dir name as a wchar string (non-null terminated)
  1384. $dir_long = join("\0", str_split($dir_long));
  1385. $dir_long = $dir_long . "\0";
  1386. // Pack the lengths of the dir strings
  1387. $dir_short_len = pack("V", strlen($dir_short) );
  1388. $dir_long_len = pack("V", strlen($dir_long) );
  1389. $stream_len = pack("V", strlen($dir_long) + 0x06);
  1390. // Pack the undocumented parts of the hyperlink stream
  1391. $unknown1 = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000' );
  1392. $unknown2 = pack("H*",'0303000000000000C000000000000046' );
  1393. $unknown3 = pack("H*",'FFFFADDE000000000000000000000000000000000000000');
  1394. $unknown4 = pack("v", 0x03 );
  1395. // Pack the main data stream
  1396. $data = pack("vvvv", $row1, $row2, $col1, $col2) .
  1397. $unknown1 .
  1398. $link_type .
  1399. $unknown2 .
  1400. $up_count .
  1401. $dir_short_len.
  1402. $dir_short .
  1403. $unknown3 .
  1404. $stream_len .
  1405. $dir_long_len .
  1406. $unknown4 .
  1407. $dir_long .
  1408. $sheet_len .
  1409. $sheet ;
  1410. // Pack the header data
  1411. $length = strlen($data);
  1412. $header = pack("vv", $record, $length);
  1413. // Write the packed data
  1414. $this->_append($header. $data);
  1415. return($str_error);
  1416. }
  1417. /*
  1418. ###############################################################################
  1419. #
  1420. # write_url_xxx($row1, $col1, $row2, $col2, $url, $string, $format)
  1421. #
  1422. # Write links to external MS/Novell network drives and shares such as
  1423. # '//NETWORK/share/foo.xls' and '//NETWORK/share/foo.xls#Sheet1!A1'.
  1424. #
  1425. # See also write_url() above for a general description and return values.
  1426. #
  1427. sub _write_url_external_net {
  1428. my $this = shift;
  1429. my $record = 0x01B8; # Record identifier
  1430. my $length = 0x00000; # Bytes to follow
  1431. my $row1 = $_[0]; # Start row
  1432. my $col1 = $_[1]; # Start column
  1433. my $row2 = $_[2]; # End row
  1434. my $col2 = $_[3]; # End column
  1435. my $url = $_[4]; # URL string
  1436. my $str = $_[5]; # Alternative label
  1437. my $xf = $_[6] || $this->{_url_format};# The cell format
  1438. # Strip URL type and change Unix dir separator to Dos style (if needed)
  1439. #
  1440. $url =~ s[^external:][];
  1441. $url =~ s[/][\\]g;
  1442. # Write the visible label
  1443. ($str = $url) =~ s[\#][ - ] unless defined $str;
  1444. my $str_error = $this->write_string($row1, $col1, $str, $xf);
  1445. return $str_error if $str_error == -2;
  1446. # Determine if the link contains a sheet reference and change some of the
  1447. # parameters accordingly.
  1448. # Split the dir name and sheet name (if it exists)
  1449. #
  1450. my ($dir_long , $sheet) = split /\#/, $url;
  1451. my $link_type = 0x0103; # Always absolute
  1452. my $sheet_len;
  1453. if (defined $sheet) {
  1454. $link_type |= 0x08;
  1455. $sheet_len = pack("V", length($sheet) + 0x01);
  1456. $sheet = join("\0", str_split($sheet));
  1457. $sheet .= "\0\0\0";
  1458. }
  1459. else {
  1460. $sheet_len = '';
  1461. $sheet = '';
  1462. }
  1463. # Pack the link type
  1464. $link_type = pack("V", $link_type);
  1465. # Make the string null terminated
  1466. $dir_long = $dir_long . "\0";
  1467. # Pack the lengths of the dir string
  1468. my $dir_long_len = pack("V", length $dir_long);
  1469. # Store the long dir name as a wchar string (non-null terminated)
  1470. $dir_long = join("\0", str_split($dir_long));
  1471. $dir_long = $dir_long . "\0";
  1472. # Pack the undocumented part of the hyperlink stream
  1473. my $unknown1 = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000');
  1474. # Pack the main data stream
  1475. my $data = pack("vvvv", $row1, $row2, $col1, $col2) .
  1476. $unknown1 .
  1477. $link_type .
  1478. $dir_long_len .
  1479. $dir_long .
  1480. $sheet_len .
  1481. $sheet ;
  1482. # Pack the header data
  1483. $length = length $data;
  1484. my $header = pack("vv", $record, $length);
  1485. # Write the packed data
  1486. $this->_append( $header, $data);
  1487. return $str_error;
  1488. }*/
  1489. /**
  1490. * This method is used to set the height and XF format for a row.
  1491. * Writes the BIFF record ROW.
  1492. *
  1493. * @access public
  1494. * @param integer $row The row to set
  1495. * @param integer $height Height we are giving to the row.
  1496. * Use NULL to set XF without setting height
  1497. * @param mixed $format XF format we are giving to the row
  1498. */
  1499. function set_row($row, $height, $format = 0)
  1500. {
  1501. $record = 0x0208; // Record identifier
  1502. $length = 0x0010; // Number of bytes to follow
  1503. $colMic = 0x0000; // First defined column
  1504. $colMac = 0x0000; // Last defined column
  1505. $irwMac = 0x0000; // Used by Excel to optimise loading
  1506. $reserved = 0x0000; // Reserved
  1507. $grbit = 0x01C0; // Option flags. (monkey) see $1 do
  1508. $ixfe = $this->_XF($format); // XF index
  1509. // Use set_row($row, NULL, $XF) to set XF without setting height
  1510. if ($height != NULL) {
  1511. $miyRw = $height * 20; // row height
  1512. }
  1513. else {
  1514. $miyRw = 0xff; // default row height is 256
  1515. }
  1516. $header = pack("vv", $record, $length);
  1517. $data = pack("vvvvvvvv", $row, $colMic, $colMac, $miyRw,
  1518. $irwMac,$reserved, $grbit, $ixfe);
  1519. $this->_append($header.$data);
  1520. }
  1521. /**
  1522. * Writes Excel DIMENSIONS to define the area in which there is data.
  1523. */
  1524. function _store_dimensions()
  1525. {
  1526. $record = 0x0000; // Record identifier
  1527. $length = 0x000A; // Number of bytes to follow
  1528. $row_min = $this->dim_rowmin; // First row
  1529. $row_max = $this->dim_rowmax; // Last row plus 1
  1530. $col_min = $this->dim_colmin; // First column
  1531. $col_max = $this->dim_colmax; // Last column plus 1
  1532. $reserved = 0x0000; // Reserved by Excel
  1533. $header = pack("vv", $record, $length);
  1534. $data = pack("vvvvv", $row_min, $row_max,
  1535. $col_min, $col_max, $reserved);
  1536. $this->_prepend($header.$data);
  1537. }
  1538. /**
  1539. * Write BIFF record Window2.
  1540. */
  1541. function _store_window2()
  1542. {
  1543. $record = 0x023E; // Record identifier
  1544. $length = 0x000A; // Number of bytes to follow
  1545. $grbit = 0x00B6; // Option flags
  1546. $rwTop = 0x0000; // Top row visible in window
  1547. $colLeft = 0x0000; // Leftmost column visible in window
  1548. $rgbHdr = 0x00000000; // Row/column heading and gridline color
  1549. // The options flags that comprise $grbit
  1550. $fDspFmla = 0; // 0 - bit
  1551. $fDspGrid = 1; // 1
  1552. $fDspRwCol = 1; // 2
  1553. $fFrozen = $this->_frozen; // 3
  1554. $fDspZeros = 1; // 4
  1555. $fDefaultHdr = 1; // 5
  1556. $fArabic = 0; // 6
  1557. $fDspGuts = 1; // 7
  1558. $fFrozenNoSplit = 0; // 0 - bit
  1559. $fSelected = $this->selected; // 1
  1560. $fPaged = 1; // 2
  1561. $grbit = $fDspFmla;
  1562. $grbit |= $fDspGrid << 1;
  1563. $grbit |= $fDspRwCol << 2;
  1564. $grbit |= $fFrozen << 3;
  1565. $grbit |= $fDspZeros << 4;
  1566. $grbit |= $fDefaultHdr << 5;
  1567. $grbit |= $fArabic << 6;
  1568. $grbit |= $fDspGuts << 7;
  1569. $grbit |= $fFrozenNoSplit << 8;
  1570. $grbit |= $fSelected << 9;
  1571. $grbit |= $fPaged << 10;
  1572. $header = pack("vv", $record, $length);
  1573. $data = pack("vvvV", $grbit, $rwTop, $colLeft, $rgbHdr);
  1574. $this->_append($header.$data);
  1575. }
  1576. /**
  1577. * Write BIFF record DEFCOLWIDTH if COLINFO records are in use.
  1578. */
  1579. function _store_defcol()
  1580. {
  1581. $record = 0x0055; // Record identifier
  1582. $length = 0x0002; // Number of bytes to follow
  1583. $colwidth = 0x0008; // Default column width
  1584. $header = pack("vv", $record, $length);
  1585. $data = pack("v", $colwidth);
  1586. $this->_prepend($header.$data);
  1587. }
  1588. /**
  1589. * Write BIFF record COLINFO to define column widths
  1590. *
  1591. * Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
  1592. * length record.
  1593. *
  1594. * @param array $col_array This is the only parameter received and is composed of the following:
  1595. * 0 => First formatted column,
  1596. * 1 => Last formatted column,
  1597. * 2 => Col width (8.43 is Excel default),
  1598. * 3 => The optional XF format of the column,
  1599. * 4 => Option flags.
  1600. */
  1601. function _store_colinfo($col_array)
  1602. {
  1603. if(isset($col_array[0])) {
  1604. $colFirst = $col_array[0];
  1605. }
  1606. if(isset($col_array[1])) {
  1607. $colLast = $col_array[1];
  1608. }
  1609. if(isset($col_array[2])) {
  1610. $coldx = $col_array[2];
  1611. }
  1612. else {
  1613. $coldx = 8.43;
  1614. }
  1615. if(isset($col_array[3])) {
  1616. $format = $col_array[3];
  1617. }
  1618. else {
  1619. $format = 0;
  1620. }
  1621. if(isset($col_array[4])) {
  1622. $grbit = $col_array[4];
  1623. }
  1624. else {
  1625. $grbit = 0;
  1626. }
  1627. $record = 0x007D; // Record identifier
  1628. $length = 0x000B; // Number of bytes to follow
  1629. $coldx += 0.72; // Fudge. Excel subtracts 0.72 !?
  1630. $coldx *= 256; // Convert to units of 1/256 of a char
  1631. $ixfe = $this->_XF($format);
  1632. $reserved = 0x00; // Reserved
  1633. $header = pack("vv", $record, $length);
  1634. $data = pack("vvvvvC", $colFirst, $colLast, $coldx,
  1635. $ixfe, $grbit, $reserved);
  1636. $this->_prepend($header.$data);
  1637. }
  1638. /**
  1639. * Write BIFF record SELECTION.
  1640. *
  1641. * @param array $array array containing ($rwFirst,$colFirst,$rwLast,$colLast)
  1642. * @see set_selection()
  1643. */
  1644. function _store_selection($array)
  1645. {
  1646. list($rwFirst,$colFirst,$rwLast,$colLast) = $array;
  1647. $record = 0x001D; // Record identifier
  1648. $length = 0x000F; // Number of bytes to follow
  1649. $pnn = $this->_active_pane; // Pane position
  1650. $rwAct = $rwFirst; // Active row
  1651. $colAct = $colFirst; // Active column
  1652. $irefAct = 0; // Active cell ref
  1653. $cref = 1; // Number of refs
  1654. if (!isset($rwLast)) {
  1655. $rwLast = $rwFirst; // Last row in reference
  1656. }
  1657. if (!isset($colLast)) {
  1658. $colLast = $colFirst; // Last col in reference
  1659. }
  1660. // Swap last row/col for first row/col as necessary
  1661. if ($rwFirst > $rwLast)
  1662. {
  1663. list($rwFirst, $rwLast) = array($rwLast, $rwFirst);
  1664. }
  1665. if ($colFirst > $colLast)
  1666. {
  1667. list($colFirst, $colLast) = array($colLast, $colFirst);
  1668. }
  1669. $header = pack("vv", $record, $length);
  1670. $data = pack("CvvvvvvCC", $pnn, $rwAct, $colAct,
  1671. $irefAct, $cref,
  1672. $rwFirst, $rwLast,
  1673. $colFirst, $colLast);
  1674. $this->_append($header.$data);
  1675. }
  1676. /**
  1677. * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
  1678. * references in a worksheet.
  1679. *
  1680. * Excel only stores references to external sheets that are used in formulas.
  1681. * For simplicity we store references to all the sheets in the workbook
  1682. * regardless of whether they are used or not. This reduces the overall
  1683. * complexity and eliminates the need for a two way dialogue between the formula
  1684. * parser the worksheet objects.
  1685. *
  1686. * @param integer $count The number of external sheet references in this worksheet
  1687. */
  1688. function _store_externcount($count)
  1689. {
  1690. $record = 0x0016; // Record identifier
  1691. $length = 0x0002; // Number of bytes to follow
  1692. $header = pack("vv", $record, $length);
  1693. $data = pack("v", $count);
  1694. $this->_prepend($header.$data);
  1695. }
  1696. /**
  1697. * Writes the Excel BIFF EXTERNSHEET record. These references are used by
  1698. * formulas. A formula references a sheet name via an index. Since we store a
  1699. * reference to all of the external worksheets the EXTERNSHEET index is the same
  1700. * as the worksheet index.
  1701. *
  1702. * @param string $sheetname The name of a external worksheet
  1703. */
  1704. function _store_externsheet($sheetname)
  1705. {
  1706. $record = 0x0017; // Record identifier
  1707. // References to the current sheet are encoded differently to references to
  1708. // external sheets.
  1709. //
  1710. if ($this->name == $sheetname) {
  1711. $sheetname = '';
  1712. $length = 0x02; // The following 2 bytes
  1713. $cch = 1; // The following byte
  1714. $rgch = 0x02; // Self reference
  1715. }
  1716. else {
  1717. $length = 0x02 + strlen($sheetname);
  1718. $cch = strlen($sheetname);
  1719. $rgch = 0x03; // Reference to a sheet in the current workbook
  1720. }
  1721. $header = pack("vv", $record, $length);
  1722. $data = pack("CC", $cch, $rgch);
  1723. $this->_prepend($header.$data.$sheetname);
  1724. }
  1725. /**
  1726. * Writes the Excel BIFF PANE record.
  1727. * The panes can either be frozen or thawed (unfrozen).
  1728. * Frozen panes are specified in terms of an integer number of rows and columns.
  1729. * Thawed panes are specified in terms of Excel's units for rows and columns.
  1730. *
  1731. * @param array $panes This is the only parameter received and is composed of the following:
  1732. * 0 => Vertical split position,
  1733. * 1 => Horizontal split position
  1734. * 2 => Top row visible
  1735. * 3 => Leftmost column visible
  1736. * 4 => Active pane
  1737. */
  1738. function _store_panes($panes)
  1739. {
  1740. $y = $panes[0];
  1741. $x = $panes[1];
  1742. $rwTop = $panes[2];
  1743. $colLeft = $panes[3];
  1744. if(count($panes) > 4) { // if Active pane was received
  1745. $pnnAct = $panes[4];
  1746. }
  1747. else {
  1748. $pnnAct = NULL;
  1749. }
  1750. $record = 0x0041; // Record identifier
  1751. $length = 0x000A; // Number of bytes to follow
  1752. // Code specific to frozen or thawed panes.
  1753. if ($this->_frozen) {
  1754. // Set default values for $rwTop and $colLeft
  1755. if(!isset($rwTop)) {
  1756. $rwTop = $y;
  1757. }
  1758. if(!isset($colLeft)) {
  1759. $colLeft = $x;
  1760. }
  1761. }
  1762. else {
  1763. // Set default values for $rwTop and $colLeft
  1764. if(!isset($rwTop)) {
  1765. $rwTop = 0;
  1766. }
  1767. if(!isset($colLeft)) {
  1768. $colLeft = 0;
  1769. }
  1770. // Convert Excel's row and column units to the internal units.
  1771. // The default row height is 12.75
  1772. // The default column width is 8.43
  1773. // The following slope and intersection values were interpolated.
  1774. //
  1775. $y = 20*$y + 255;
  1776. $x = 113.879*$x + 390;
  1777. }
  1778. // Determine which pane should be active. There is also the undocumented
  1779. // option to override this should it be necessary: may be removed later.
  1780. //
  1781. if (!isset($pnnAct))
  1782. {
  1783. if ($x != 0 and $y != 0)
  1784. $pnnAct = 0; // Bottom right
  1785. if ($x != 0 and $y == 0)
  1786. $pnnAct = 1; // Top right
  1787. if ($x == 0 and $y != 0)
  1788. $pnnAct = 2; // Bottom left
  1789. if ($x == 0 and $y == 0)
  1790. $pnnAct = 3; // Top left
  1791. }
  1792. $this->_active_pane = $pnnAct; // Used in _store_selection
  1793. $header = pack("vv", $record, $length);
  1794. $data = pack("vvvvv", $x, $y, $rwTop, $colLeft, $pnnAct);
  1795. $this->_append($header.$data);
  1796. }
  1797. /**
  1798. * Store the page setup SETUP BIFF record.
  1799. */
  1800. function _store_setup()
  1801. {
  1802. $record = 0x00A1; // Record identifier
  1803. $length = 0x0022; // Number of bytes to follow
  1804. $iPaperSize = $this->_paper_size; // Paper size
  1805. $iScale = $this->_print_scale; // Print scaling factor
  1806. $iPageStart = 0x01; // Starting page number
  1807. $iFitWidth = $this->_fit_width; // Fit to number of pages wide
  1808. $iFitHeight = $this->_fit_height; // Fit to number of pages high
  1809. $grbit = 0x00; // Option flags
  1810. $iRes = 0x0258; // Print resolution
  1811. $iVRes = 0x0258; // Vertical print resolution
  1812. $numHdr = $this->_margin_head; // Header Margin
  1813. $numFtr = $this->_margin_foot; // Footer Margin
  1814. $iCopies = 0x01; // Number of copies
  1815. $fLeftToRight = 0x0; // Print over then down
  1816. $fLandscape = $this->_orientation; // Page orientation
  1817. $fNoPls = 0x0; // Setup not read from printer
  1818. $fNoColor = 0x0; // Print black and white
  1819. $fDraft = 0x0; // Print draft quality
  1820. $fNotes = 0x0; // Print notes
  1821. $fNoOrient = 0x0; // Orientation not set
  1822. $fUsePage = 0x0; // Use custom starting page
  1823. $grbit = $fLeftToRight;
  1824. $grbit |= $fLandscape << 1;
  1825. $grbit |= $fNoPls << 2;
  1826. $grbit |= $fNoColor << 3;
  1827. $grbit |= $fDraft << 4;
  1828. $grbit |= $fNotes << 5;
  1829. $grbit |= $fNoOrient << 6;
  1830. $grbit |= $fUsePage << 7;
  1831. $numHdr = pack("d", $numHdr);
  1832. $numFtr = pack("d", $numFtr);
  1833. if ($this->_byte_order) // if it's Big Endian
  1834. {
  1835. $numHdr = strrev($numHdr);
  1836. $numFtr = strrev($numFtr);
  1837. }
  1838. $header = pack("vv", $record, $length);
  1839. $data1 = pack("vvvvvvvv", $iPaperSize,
  1840. $iScale,
  1841. $iPageStart,
  1842. $iFitWidth,
  1843. $iFitHeight,
  1844. $grbit,
  1845. $iRes,
  1846. $iVRes);
  1847. $data2 = $numHdr .$numFtr;
  1848. $data3 = pack("v", $iCopies);
  1849. $this->_prepend($header.$data1.$data2.$data3);
  1850. }
  1851. /**
  1852. * Store the header caption BIFF record.
  1853. */
  1854. function store_header()
  1855. {
  1856. $record = 0x0014; // Record identifier
  1857. $str = $this->_header; // header string
  1858. $cch = strlen($str); // Length of header string
  1859. $length = 1 + $cch; // Bytes to follow
  1860. $header = pack("vv", $record, $length);
  1861. $data = pack("C", $cch);
  1862. $this->_append($header.$data.$str);
  1863. }
  1864. /**
  1865. * Store the footer caption BIFF record.
  1866. */
  1867. function store_footer()
  1868. {
  1869. $record = 0x0015; // Record identifier
  1870. $str = $this->_footer; // Footer string
  1871. $cch = strlen($str); // Length of footer string
  1872. $length = 1 + $cch; // Bytes to follow
  1873. $header = pack("vv", $record, $length);
  1874. $data = pack("C", $cch);
  1875. $this->_append($header.$data.$str);
  1876. }
  1877. /**
  1878. * Store the horizontal centering HCENTER BIFF record.
  1879. */
  1880. function store_hcenter()
  1881. {
  1882. $record = 0x0083; // Record identifier
  1883. $length = 0x0002; // Bytes to follow
  1884. $fHCenter = $this->_hcenter; // Horizontal centering
  1885. $header = pack("vv", $record, $length);
  1886. $data = pack("v", $fHCenter);
  1887. $this->_append($header.$data);
  1888. }
  1889. /**
  1890. * Store the vertical centering VCENTER BIFF record.
  1891. */
  1892. function store_vcenter()
  1893. {
  1894. $record = 0x0084; // Record identifier
  1895. $length = 0x0002; // Bytes to follow
  1896. $fVCenter = $this->_vcenter; // Horizontal centering
  1897. $header = pack("vv", $record, $length);
  1898. $data = pack("v", $fVCenter);
  1899. $this->_append($header.$data);
  1900. }
  1901. /**
  1902. * Store the LEFTMARGIN BIFF record.
  1903. */
  1904. function _store_margin_left()
  1905. {
  1906. $record = 0x0026; // Record identifier
  1907. $length = 0x0008; // Bytes to follow
  1908. $margin = $this->_margin_left; // Margin in inches
  1909. $header = pack("vv", $record, $length);
  1910. $data = pack("d", $margin);
  1911. if ($this->_byte_order) // if it's Big Endian
  1912. {
  1913. $data = strrev($data);
  1914. }
  1915. $this->_append($header.$data);
  1916. }
  1917. /**
  1918. * Store the RIGHTMARGIN BIFF record.
  1919. */
  1920. function _store_margin_right()
  1921. {
  1922. $record = 0x0027; // Record identifier
  1923. $length = 0x0008; // Bytes to follow
  1924. $margin = $this->_margin_right; // Margin in inches
  1925. $header = pack("vv", $record, $length);
  1926. $data = pack("d", $margin);
  1927. if ($this->_byte_order) // if it's Big Endian
  1928. {
  1929. $data = strrev($data);
  1930. }
  1931. $this->_append($header.$data);
  1932. }
  1933. /**
  1934. * Store the TOPMARGIN BIFF record.
  1935. */
  1936. function _store_margin_top()
  1937. {
  1938. $record = 0x0028; // Record identifier
  1939. $length = 0x0008; // Bytes to follow
  1940. $margin = $this->_margin_top; // Margin in inches
  1941. $header = pack("vv", $record, $length);
  1942. $data = pack("d", $margin);
  1943. if ($this->_byte_order) // if it's Big Endian
  1944. {
  1945. $data = strrev($data);
  1946. }
  1947. $this->_append($header.$data);
  1948. }
  1949. /**
  1950. * Store the BOTTOMMARGIN BIFF record.
  1951. */
  1952. function _store_margin_bottom()
  1953. {
  1954. $record = 0x0029; // Record identifier
  1955. $length = 0x0008; // Bytes to follow
  1956. $margin = $this->_margin_bottom; // Margin in inches
  1957. $header = pack("vv", $record, $length);
  1958. $data = pack("d", $margin);
  1959. if ($this->_byte_order) // if it's Big Endian
  1960. {
  1961. $data = strrev($data);
  1962. }
  1963. $this->_append($header.$data);
  1964. }
  1965. /**
  1966. * This is an Excel97/2000 method. It is required to perform more complicated
  1967. * merging than the normal set_align('merge'). It merges the area given by
  1968. * its arguments.
  1969. *
  1970. * @access public
  1971. * @param integer $first_row First row of the area to merge
  1972. * @param integer $first_col First column of the area to merge
  1973. * @param integer $last_row Last row of the area to merge
  1974. * @param integer $last_col Last column of the area to merge
  1975. */
  1976. function merge_cells($first_row, $first_col, $last_row, $last_col)
  1977. {
  1978. $record = 0x00E5; // Record identifier
  1979. $length = 0x000A; // Bytes to follow
  1980. $cref = 1; // Number of refs
  1981. // Swap last row/col for first row/col as necessary
  1982. if ($first_row > $last_row) {
  1983. list($first_row, $last_row) = array($last_row, $first_row);
  1984. }
  1985. if ($first_col > $last_col) {
  1986. list($first_col, $last_col) = array($last_col, $first_col);
  1987. }
  1988. $header = pack("vv", $record, $length);
  1989. $data = pack("vvvvv", $cref, $first_row, $last_row,
  1990. $first_col, $last_col);
  1991. $this->_append($header.$data);
  1992. }
  1993. /**
  1994. * Write the PRINTHEADERS BIFF record.
  1995. */
  1996. function _store_print_headers()
  1997. {
  1998. $record = 0x002a; // Record identifier
  1999. $length = 0x0002; // Bytes to follow
  2000. $fPrintRwCol = $this->_print_headers; // Boolean flag
  2001. $header = pack("vv", $record, $length);
  2002. $data = pack("v", $fPrintRwCol);
  2003. $this->_prepend($header.$data);
  2004. }
  2005. /**
  2006. * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the
  2007. * GRIDSET record.
  2008. */
  2009. function _store_print_gridlines()
  2010. {
  2011. $record = 0x002b; // Record identifier
  2012. $length = 0x0002; // Bytes to follow
  2013. $fPrintGrid = $this->_print_gridlines; // Boolean flag
  2014. $header = pack("vv", $record, $length);
  2015. $data = pack("v", $fPrintGrid);
  2016. $this->_prepend($header.$data);
  2017. }
  2018. /**
  2019. * Write the GRIDSET BIFF record. Must be used in conjunction with the
  2020. * PRINTGRIDLINES record.
  2021. */
  2022. function _store_gridset()
  2023. {
  2024. $record = 0x0082; // Record identifier
  2025. $length = 0x0002; // Bytes to follow
  2026. $fGridSet = !($this->_print_gridlines); // Boolean flag
  2027. $header = pack("vv", $record, $length);
  2028. $data = pack("v", $fGridSet);
  2029. $this->_prepend($header.$data);
  2030. }
  2031. /**
  2032. * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction
  2033. * with the SETUP record.
  2034. */
  2035. function _store_wsbool()
  2036. {
  2037. $record = 0x0081; // Record identifier
  2038. $length = 0x0002; // Bytes to follow
  2039. // The only option that is of interest is the flag for fit to page. So we
  2040. // set all the options in one go.
  2041. //
  2042. if ($this->_fit_page) {
  2043. $grbit = 0x05c1;
  2044. }
  2045. else {
  2046. $grbit = 0x04c1;
  2047. }
  2048. $header = pack("vv", $record, $length);
  2049. $data = pack("v", $grbit);
  2050. $this->_prepend($header.$data);
  2051. }
  2052. /**
  2053. * Write the HORIZONTALPAGEBREAKS BIFF record.
  2054. */
  2055. function _store_hbreak()
  2056. {
  2057. // Return if the user hasn't specified pagebreaks
  2058. if(empty($this->_hbreaks)) {
  2059. return;
  2060. }
  2061. // Sort and filter array of page breaks
  2062. $breaks = $this->_hbreaks;
  2063. sort($breaks,SORT_NUMERIC);
  2064. if($breaks[0] == 0) { // don't use first break if it's 0
  2065. array_shift($breaks);
  2066. }
  2067. $record = 0x001b; // Record identifier
  2068. $cbrk = count($breaks); // Number of page breaks
  2069. $length = ($cbrk + 1) * 2; // Bytes to follow
  2070. $header = pack("vv", $record, $length);
  2071. $data = pack("v", $cbrk);
  2072. // Append each page break
  2073. foreach($breaks as $break) {
  2074. $data .= pack("v", $break);
  2075. }
  2076. $this->_prepend($header.$data);
  2077. }
  2078. /**
  2079. * Write the VERTICALPAGEBREAKS BIFF record.
  2080. */
  2081. function _store_vbreak()
  2082. {
  2083. // Return if the user hasn't specified pagebreaks
  2084. if(empty($this->_vbreaks)) {
  2085. return;
  2086. }
  2087. // 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
  2088. // It is slightly higher in Excel 97/200, approx. 1026
  2089. $breaks = array_slice($this->_vbreaks,0,1000);
  2090. // Sort and filter array of page breaks
  2091. sort($breaks,SORT_NUMERIC);
  2092. if($breaks[0] == 0) { // don't use first break if it's 0
  2093. array_shift($breaks);
  2094. }
  2095. $record = 0x001a; // Record identifier
  2096. $cbrk = count($breaks); // Number of page breaks
  2097. $length = ($cbrk + 1) * 2; // Bytes to follow
  2098. $header = pack("vv", $record, $length);
  2099. $data = pack("v", $cbrk);
  2100. // Append each page break
  2101. foreach ($breaks as $break) {
  2102. $data .= pack("v", $break);
  2103. }
  2104. $this->_prepend($header.$data);
  2105. }
  2106. /**
  2107. * Set the Biff PROTECT record to indicate that the worksheet is protected.
  2108. */
  2109. function _store_protect()
  2110. {
  2111. // Exit unless sheet protection has been specified
  2112. if($this->_protect == 0) {
  2113. return;
  2114. }
  2115. $record = 0x0012; // Record identifier
  2116. $length = 0x0002; // Bytes to follow
  2117. $fLock = $this->_protect; // Worksheet is protected
  2118. $header = pack("vv", $record, $length);
  2119. $data = pack("v", $fLock);
  2120. $this->_prepend($header.$data);
  2121. }
  2122. /**
  2123. * Write the worksheet PASSWORD record.
  2124. */
  2125. function _store_password()
  2126. {
  2127. // Exit unless sheet protection and password have been specified
  2128. if(($this->_protect == 0) or (!isset($this->_password))) {
  2129. return;
  2130. }
  2131. $record = 0x0013; // Record identifier
  2132. $length = 0x0002; // Bytes to follow
  2133. $wPassword = $this->_password; // Encoded password
  2134. $header = pack("vv", $record, $length);
  2135. $data = pack("v", $wPassword);
  2136. $this->_prepend($header.$data);
  2137. }
  2138. /**
  2139. * Insert a 24bit bitmap image in a worksheet. The main record required is
  2140. * IMDATA but it must be proceeded by a OBJ record to define its position.
  2141. *
  2142. * @access public
  2143. * @param integer $row The row we are going to insert the bitmap into
  2144. * @param integer $col The column we are going to insert the bitmap into
  2145. * @param string $bitmap The bitmap filename
  2146. * @param integer $x The horizontal position (offset) of the image inside the cell.
  2147. * @param integer $y The vertical position (offset) of the image inside the cell.
  2148. * @param integer $scale_x The horizontal scale
  2149. * @param integer $scale_y The vertical scale
  2150. */
  2151. function insert_bitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1)
  2152. {
  2153. list($width, $height, $size, $data) = $this->_process_bitmap($bitmap);
  2154. // Scale the frame of the image.
  2155. $width *= $scale_x;
  2156. $height *= $scale_y;
  2157. // Calculate the vertices of the image and write the OBJ record
  2158. $this->_position_image($col, $row, $x, $y, $width, $height);
  2159. // Write the IMDATA record to store the bitmap data
  2160. $record = 0x007f;
  2161. $length = 8 + $size;
  2162. $cf = 0x09;
  2163. $env = 0x01;
  2164. $lcb = $size;
  2165. $header = pack("vvvvV", $record, $length, $cf, $env, $lcb);
  2166. $this->_append($header.$data);
  2167. }
  2168. /**
  2169. * Calculate the vertices that define the position of the image as required by
  2170. * the OBJ record.
  2171. *
  2172. * +------------+------------+
  2173. * | A | B |
  2174. * +-----+------------+------------+
  2175. * | |(x1,y1) | |
  2176. * | 1 |(A1)._______|______ |
  2177. * | | | | |
  2178. * | | | | |
  2179. * +-----+----| BITMAP |-----+
  2180. * | | | | |
  2181. * | 2 | |______________. |
  2182. * | | | (B2)|
  2183. * | | | (x2,y2)|
  2184. * +---- +------------+------------+
  2185. *
  2186. * Example of a bitmap that covers some of the area from cell A1 to cell B2.
  2187. *
  2188. * Based on the width and height of the bitmap we need to calculate 8 vars:
  2189. * $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
  2190. * The width and height of the cells are also variable and have to be taken into
  2191. * account.
  2192. * The values of $col_start and $row_start are passed in from the calling
  2193. * function. The values of $col_end and $row_end are calculated by subtracting
  2194. * the width and height of the bitmap from the width and height of the
  2195. * underlying cells.
  2196. * The vertices are expressed as a percentage of the underlying cell width as
  2197. * follows (rhs values are in pixels):
  2198. *
  2199. * x1 = X / W *1024
  2200. * y1 = Y / H *256
  2201. * x2 = (X-1) / W *1024
  2202. * y2 = (Y-1) / H *256
  2203. *
  2204. * Where: X is distance from the left side of the underlying cell
  2205. * Y is distance from the top of the underlying cell
  2206. * W is the width of the cell
  2207. * H is the height of the cell
  2208. *
  2209. * @note the SDK incorrectly states that the height should be expressed as a
  2210. * percentage of 1024.
  2211. * @param integer $col_start Col containing upper left corner of object
  2212. * @param integer $row_start Row containing top left corner of object
  2213. * @param integer $x1 Distance to left side of object
  2214. * @param integer $y1 Distance to top of object
  2215. * @param integer $width Width of image frame
  2216. * @param integer $height Height of image frame
  2217. */
  2218. function _position_image($col_start, $row_start, $x1, $y1, $width, $height)
  2219. {
  2220. // Initialise end cell to the same as the start cell
  2221. $col_end = $col_start; // Col containing lower right corner of object
  2222. $row_end = $row_start; // Row containing bottom right corner of object
  2223. // Zero the specified offset if greater than the cell dimensions
  2224. if ($x1 >= $this->size_col($col_start))
  2225. {
  2226. $x1 = 0;
  2227. }
  2228. if ($y1 >= $this->size_row($row_start))
  2229. {
  2230. $y1 = 0;
  2231. }
  2232. $width = $width + $x1 -1;
  2233. $height = $height + $y1 -1;
  2234. // Subtract the underlying cell widths to find the end cell of the image
  2235. while ($width >= $this->size_col($col_end)) {
  2236. $width -= $this->size_col($col_end);
  2237. $col_end++;
  2238. }
  2239. // Subtract the underlying cell heights to find the end cell of the image
  2240. while ($height >= $this->size_row($row_end)) {
  2241. $height -= $this->size_row($row_end);
  2242. $row_end++;
  2243. }
  2244. // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
  2245. // with zero eight or width.
  2246. //
  2247. if ($this->size_col($col_start) == 0)
  2248. return;
  2249. if ($this->size_col($col_end) == 0)
  2250. return;
  2251. if ($this->size_row($row_start) == 0)
  2252. return;
  2253. if ($this->size_row($row_end) == 0)
  2254. return;
  2255. // Convert the pixel values to the percentage value expected by Excel
  2256. $x1 = $x1 / $this->size_col($col_start) * 1024;
  2257. $y1 = $y1 / $this->size_row($row_start) * 256;
  2258. $x2 = $width / $this->size_col($col_end) * 1024; // Distance to right side of object
  2259. $y2 = $height / $this->size_row($row_end) * 256; // Distance to bottom of object
  2260. $this->_store_obj_picture( $col_start, $x1,
  2261. $row_start, $y1,
  2262. $col_end, $x2,
  2263. $row_end, $y2
  2264. );
  2265. }
  2266. /**
  2267. * Convert the width of a cell from user's units to pixels. By interpolation
  2268. * the relationship is: y = 7x +5. If the width hasn't been set by the user we
  2269. * use the default value. If the col is hidden we use a value of zero.
  2270. *
  2271. * @param integer $col The column
  2272. * @return integer The width in pixels
  2273. */
  2274. function size_col($col)
  2275. {
  2276. // Look up the cell value to see if it has been changed
  2277. if (isset($this->col_sizes[$col])) {
  2278. if ($this->col_sizes[$col] == 0) {
  2279. return(0);
  2280. }
  2281. else {
  2282. return(floor(7 * $this->col_sizes[$col] + 5));
  2283. }
  2284. }
  2285. else {
  2286. return(64);
  2287. }
  2288. }
  2289. /**
  2290. * Convert the height of a cell from user's units to pixels. By interpolation
  2291. * the relationship is: y = 4/3x. If the height hasn't been set by the user we
  2292. * use the default value. If the row is hidden we use a value of zero. (Not
  2293. * possible to hide row yet).
  2294. *
  2295. * @param integer $row The row
  2296. * @return integer The width in pixels
  2297. */
  2298. function size_row($row)
  2299. {
  2300. // Look up the cell value to see if it has been changed
  2301. if (isset($this->row_sizes[$row])) {
  2302. if ($this->row_sizes[$row] == 0) {
  2303. return(0);
  2304. }
  2305. else {
  2306. return(floor(4/3 * $this->row_sizes[$row]));
  2307. }
  2308. }
  2309. else {
  2310. return(17);
  2311. }
  2312. }
  2313. /**
  2314. * Store the OBJ record that precedes an IMDATA record. This could be generalise
  2315. * to support other Excel objects.
  2316. *
  2317. * @param integer $colL Column containing upper left corner of object
  2318. * @param integer $dxL Distance from left side of cell
  2319. * @param integer $rwT Row containing top left corner of object
  2320. * @param integer $dyT Distance from top of cell
  2321. * @param integer $colR Column containing lower right corner of object
  2322. * @param integer $dxR Distance from right of cell
  2323. * @param integer $rwB Row containing bottom right corner of object
  2324. * @param integer $dyB Distance from bottom of cell
  2325. */
  2326. function _store_obj_picture($colL,$dxL,$rwT,$dyT,$colR,$dxR,$rwB,$dyB)
  2327. {
  2328. $record = 0x005d; // Record identifier
  2329. $length = 0x003c; // Bytes to follow
  2330. $cObj = 0x0001; // Count of objects in file (set to 1)
  2331. $OT = 0x0008; // Object type. 8 = Picture
  2332. $id = 0x0001; // Object ID
  2333. $grbit = 0x0614; // Option flags
  2334. $cbMacro = 0x0000; // Length of FMLA structure
  2335. $Reserved1 = 0x0000; // Reserved
  2336. $Reserved2 = 0x0000; // Reserved
  2337. $icvBack = 0x09; // Background colour
  2338. $icvFore = 0x09; // Foreground colour
  2339. $fls = 0x00; // Fill pattern
  2340. $fAuto = 0x00; // Automatic fill
  2341. $icv = 0x08; // Line colour
  2342. $lns = 0xff; // Line style
  2343. $lnw = 0x01; // Line weight
  2344. $fAutoB = 0x00; // Automatic border
  2345. $frs = 0x0000; // Frame style
  2346. $cf = 0x0009; // Image format, 9 = bitmap
  2347. $Reserved3 = 0x0000; // Reserved
  2348. $cbPictFmla = 0x0000; // Length of FMLA structure
  2349. $Reserved4 = 0x0000; // Reserved
  2350. $grbit2 = 0x0001; // Option flags
  2351. $Reserved5 = 0x0000; // Reserved
  2352. $header = pack("vv", $record, $length);
  2353. $data = pack("V", $cObj);
  2354. $data .= pack("v", $OT);
  2355. $data .= pack("v", $id);
  2356. $data .= pack("v", $grbit);
  2357. $data .= pack("v", $colL);
  2358. $data .= pack("v", $dxL);
  2359. $data .= pack("v", $rwT);
  2360. $data .= pack("v", $dyT);
  2361. $data .= pack("v", $colR);
  2362. $data .= pack("v", $dxR);
  2363. $data .= pack("v", $rwB);
  2364. $data .= pack("v", $dyB);
  2365. $data .= pack("v", $cbMacro);
  2366. $data .= pack("V", $Reserved1);
  2367. $data .= pack("v", $Reserved2);
  2368. $data .= pack("C", $icvBack);
  2369. $data .= pack("C", $icvFore);
  2370. $data .= pack("C", $fls);
  2371. $data .= pack("C", $fAuto);
  2372. $data .= pack("C", $icv);
  2373. $data .= pack("C", $lns);
  2374. $data .= pack("C", $lnw);
  2375. $data .= pack("C", $fAutoB);
  2376. $data .= pack("v", $frs);
  2377. $data .= pack("V", $cf);
  2378. $data .= pack("v", $Reserved3);
  2379. $data .= pack("v", $cbPictFmla);
  2380. $data .= pack("v", $Reserved4);
  2381. $data .= pack("v", $grbit2);
  2382. $data .= pack("V", $Reserved5);
  2383. $this->_append($header.$data);
  2384. }
  2385. /**
  2386. * Convert a 24 bit bitmap into the modified internal format used by Windows.
  2387. * This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
  2388. * MSDN library.
  2389. *
  2390. * @param string $bitmap The bitmap to process
  2391. * @return array Array with data and properties of the bitmap
  2392. */
  2393. function _process_bitmap($bitmap)
  2394. {
  2395. // Open file.
  2396. $bmp_fd = fopen($bitmap,"rb");
  2397. if (!$bmp_fd) {
  2398. die("Couldn't import $bitmap");
  2399. }
  2400. // Slurp the file into a string.
  2401. $data = fread($bmp_fd, filesize($bitmap));
  2402. // Check that the file is big enough to be a bitmap.
  2403. if (strlen($data) <= 0x36) {
  2404. die("$bitmap doesn't contain enough data.\n");
  2405. }
  2406. // The first 2 bytes are used to identify the bitmap.
  2407. $identity = unpack("A2", $data);
  2408. if ($identity[''] != "BM") {
  2409. die("$bitmap doesn't appear to be a valid bitmap image.\n");
  2410. }
  2411. // Remove bitmap data: ID.
  2412. $data = substr($data, 2);
  2413. // Read and remove the bitmap size. This is more reliable than reading
  2414. // the data size at offset 0x22.
  2415. //
  2416. $size_array = unpack("V", substr($data, 0, 4));
  2417. $size = $size_array[''];
  2418. $data = substr($data, 4);
  2419. $size -= 0x36; // Subtract size of bitmap header.
  2420. $size += 0x0C; // Add size of BIFF header.
  2421. // Remove bitmap data: reserved, offset, header length.
  2422. $data = substr($data, 12);
  2423. // Read and remove the bitmap width and height. Verify the sizes.
  2424. $width_and_height = unpack("V2", substr($data, 0, 8));
  2425. $width = $width_and_height[1];
  2426. $height = $width_and_height[2];
  2427. $data = substr($data, 8);
  2428. if ($width > 0xFFFF) {
  2429. die("$bitmap: largest image width supported is 65k.\n");
  2430. }
  2431. if ($height > 0xFFFF) {
  2432. die("$bitmap: largest image height supported is 65k.\n");
  2433. }
  2434. // Read and remove the bitmap planes and bpp data. Verify them.
  2435. $planes_and_bitcount = unpack("v2", substr($data, 0, 4));
  2436. $data = substr($data, 4);
  2437. if ($planes_and_bitcount[2] != 24) { // Bitcount
  2438. die("$bitmap isn't a 24bit true color bitmap.\n");
  2439. }
  2440. if ($planes_and_bitcount[1] != 1) {
  2441. die("$bitmap: only 1 plane supported in bitmap image.\n");
  2442. }
  2443. // Read and remove the bitmap compression. Verify compression.
  2444. $compression = unpack("V", substr($data, 0, 4));
  2445. $data = substr($data, 4);
  2446. //$compression = 0;
  2447. if ($compression[""] != 0) {
  2448. die("$bitmap: compression not supported in bitmap image.\n");
  2449. }
  2450. // Remove bitmap data: data size, hres, vres, colours, imp. colours.
  2451. $data = substr($data, 20);
  2452. // Add the BITMAPCOREHEADER data
  2453. $header = pack("Vvvvv", 0x000c, $width, $height, 0x01, 0x18);
  2454. $data = $header . $data;
  2455. return (array($width, $height, $size, $data));
  2456. }
  2457. /**
  2458. * Store the window zoom factor. This should be a reduced fraction but for
  2459. * simplicity we will store all fractions with a numerator of 100.
  2460. */
  2461. function _store_zoom()
  2462. {
  2463. // If scale is 100 we don't need to write a record
  2464. if ($this->_zoom == 100) {
  2465. return;
  2466. }
  2467. $record = 0x00A0; // Record identifier
  2468. $length = 0x0004; // Bytes to follow
  2469. $header = pack("vv", $record, $length);
  2470. $data = pack("vv", $this->_zoom, 100);
  2471. $this->_append($header.$data);
  2472. }
  2473. }
  2474. ?>