PageRenderTime 67ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 1ms

/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

Large files files are truncated, but you can click here to view the full file

  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) .

Large files files are truncated, but you can click here to view the full file