Skip to content
Snippets Groups Projects
Worksheet.php 91.6 KiB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000
<?php
/*
*  Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
*
*  The majority of this is _NOT_ my code.  I simply ported it from the
*  PERL Spreadsheet::WriteExcel module.
*
*  The author of the Spreadsheet::WriteExcel module is John McNamara
*  <jmcnamara@cpan.org>
*
*  I _DO_ maintain this code, and John McNamara has nothing to do with the
*  porting of this code to PHP.  Any questions directly related to this
*  class library should be directed to me.
*
*  License Information:
*
*    Spreadsheet::WriteExcel:  A library for generating Excel Spreadsheets
*    Copyright (C) 2002 Xavier Noguer xnoguer@rezebra.com
*
*    This library is free software; you can redistribute it and/or
*    modify it under the terms of the GNU Lesser General Public
*    License as published by the Free Software Foundation; either
*    version 2.1 of the License, or (at your option) any later version.
*
*    This library is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*    Lesser General Public License for more details.
*
*    You should have received a copy of the GNU Lesser General Public
*    License along with this library; if not, write to the Free Software
*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

require_once('Parser.php');
require_once('BIFFwriter.php');

/**
* Class for generating Excel Spreadsheets
*
* @author Xavier Noguer <xnoguer@rezebra.com>
* @package Spreadsheet_WriteExcel
*/

class Worksheet extends BIFFwriter
{

    /**
    * Constructor
    *
    * @param string  $name         The name of the new worksheet
    * @param integer $index        The index of the new worksheet
    * @param mixed   &$activesheet The current activesheet of the workbook we belong to
    * @param mixed   &$firstsheet  The first worksheet in the workbook we belong to
    * @param mixed   &$url_format  The default format for hyperlinks
    * @param mixed   &$parser      The formula parser created for the Workbook
    */
    function __construct($name,$index,&$activesheet,&$firstsheet,&$url_format,&$parser)
    {
        parent::__construct();
        $rowmax                = 65536; // 16384 in Excel 5
        $colmax                = 256;
        $strmax                = 255;

        $this->name            = $name;
        $this->index           = $index;
        $this->activesheet     = &$activesheet;
        $this->firstsheet      = &$firstsheet;
        $this->_url_format     = $url_format;
        $this->_parser         = &$parser;

        $this->ext_sheets      = array();
        $this->_using_tmpfile  = 1;
        $this->_filehandle     = "";
        $this->fileclosed      = 0;
        $this->offset          = 0;
        $this->xls_rowmax      = $rowmax;
        $this->xls_colmax      = $colmax;
        $this->xls_strmax      = $strmax;
        $this->dim_rowmin      = $rowmax +1;
        $this->dim_rowmax      = 0;
        $this->dim_colmin      = $colmax +1;
        $this->dim_colmax      = 0;
        $this->colinfo         = array();
        $this->_selection      = array(0,0,0,0);
        $this->_panes          = array();
        $this->_active_pane    = 3;
        $this->_frozen         = 0;
        $this->selected        = 0;

        $this->_paper_size      = 0x0;
        $this->_orientation     = 0x1;
        $this->_header          = '';
        $this->_footer          = '';
        $this->_hcenter         = 0;
        $this->_vcenter         = 0;
        $this->_margin_head     = 0.50;
        $this->_margin_foot     = 0.50;
        $this->_margin_left     = 0.75;
        $this->_margin_right    = 0.75;
        $this->_margin_top      = 1.00;
        $this->_margin_bottom   = 1.00;

        $this->_title_rowmin    = NULL;
        $this->_title_rowmax    = NULL;
        $this->_title_colmin    = NULL;
        $this->_title_colmax    = NULL;
        $this->_print_rowmin    = NULL;
        $this->_print_rowmax    = NULL;
        $this->_print_colmin    = NULL;
        $this->_print_colmax    = NULL;

        $this->_print_gridlines = 1;
        $this->_print_headers   = 0;

        $this->_fit_page        = 0;
        $this->_fit_width       = 0;
        $this->_fit_height      = 0;

        $this->_hbreaks         = array();
        $this->_vbreaks         = array();

        $this->_protect         = 0;
        $this->_password        = NULL;

        $this->col_sizes        = array();
        $this->row_sizes        = array();

        $this->_zoom            = 100;
        $this->_print_scale     = 100;

        $this->_initialize();
    }

    /**
    * Open a tmp file to store the majority of the Worksheet data. If this fails,
    * for example due to write permissions, store the data in memory. This can be
    * slow for large files.
    */
    function _initialize()
    {
        // Open tmp file for storing Worksheet data
        $fh = tmpfile();
        if ( $fh) {
            // Store filehandle
            $this->_filehandle = $fh;
        }
        else {
            // If tmpfile() fails store data in memory
            $this->_using_tmpfile = 0;
        }
    }

    /**
    * Add data to the beginning of the workbook (note the reverse order)
    * and to the end of the workbook.
    *
    * @access public
    * @see Workbook::store_workbook()
    * @param array $sheetnames The array of sheetnames from the Workbook this
    *                          worksheet belongs to
    */
    function close($sheetnames)
    {
        $num_sheets = count($sheetnames);

        /***********************************************
        * Prepend in reverse order!!
        */

        // Prepend the sheet dimensions
        $this->_store_dimensions();

        // Prepend the sheet password
        $this->_store_password();

        // Prepend the sheet protection
        $this->_store_protect();

        // Prepend the page setup
        $this->_store_setup();

        // Prepend the bottom margin
        $this->_store_margin_bottom();

        // Prepend the top margin
        $this->_store_margin_top();

        // Prepend the right margin
        $this->_store_margin_right();

        // Prepend the left margin
        $this->_store_margin_left();

        // Prepend the page vertical centering
        $this->store_vcenter();

        // Prepend the page horizontal centering
        $this->store_hcenter();

        // Prepend the page footer
        $this->store_footer();

        // Prepend the page header
        $this->store_header();

        // Prepend the vertical page breaks
        $this->_store_vbreak();

        // Prepend the horizontal page breaks
        $this->_store_hbreak();

        // Prepend WSBOOL
        $this->_store_wsbool();

        // Prepend GRIDSET
        $this->_store_gridset();

        // Prepend PRINTGRIDLINES
        $this->_store_print_gridlines();

        // Prepend PRINTHEADERS
        $this->_store_print_headers();

        // Prepend EXTERNSHEET references
        for ($i = $num_sheets; $i > 0; $i--) {
            $sheetname = $sheetnames[$i-1];
            $this->_store_externsheet($sheetname);
        }

        // Prepend the EXTERNCOUNT of external references.
        $this->_store_externcount($num_sheets);

        // Prepend the COLINFO records if they exist
        if (!empty($this->colinfo)){
            for($i=0; $i < count($this->colinfo); $i++)
            {
                $this->_store_colinfo($this->colinfo[$i]);
            }
            $this->_store_defcol();
        }

        // Prepend the BOF record
        $this->_store_bof(0x0010);

        /*
        * End of prepend. Read upwards from here.
        ***********************************************/

        // Append
        $this->_store_window2();
        $this->_store_zoom();
        if(!empty($this->_panes))
          $this->_store_panes($this->_panes);
        $this->_store_selection($this->_selection);
        $this->_store_eof();
    }

    /**
    * Retrieve the worksheet name. This is usefull when creating worksheets
    * without a name.
    *
    * @access public
    * @return string The worksheet's name
    */
    function get_name()
    {
        return($this->name);
    }

    /**
    * Retrieves data from memory in one chunk, or from disk in $buffer
    * sized chunks.
    *
    * @return string The data
    */
    function get_data()
    {
        $buffer = 4096;

        // Return data stored in memory
        if (isset($this->_data)) {
            $tmp   = $this->_data;
            unset($this->_data);
            $fh    = $this->_filehandle;
            if ($this->_using_tmpfile) {
                fseek($fh, 0);
            }
            return($tmp);
        }
        // Return data stored on disk
        if ($this->_using_tmpfile) {
            if ($tmp = fread($this->_filehandle, $buffer)) {
                return($tmp);
            }
        }

        // No data to return
        return('');
    }

    /**
    * Set this worksheet as a selected worksheet, i.e. the worksheet has its tab
    * highlighted.
    *
    * @access public
    */
    function select()
    {
        $this->selected = 1;
    }

    /**
    * Set this worksheet as the active worksheet, i.e. the worksheet that is
    * displayed when the workbook is opened. Also set it as selected.
    *
    * @access public
    */
    function activate()
    {
        $this->selected = 1;
        $this->activesheet =& $this->index;
    }

    /**
    * Set this worksheet as the first visible sheet. This is necessary
    * when there are a large number of worksheets and the activated
    * worksheet is not visible on the screen.
    *
    * @access public
    */
    function set_first_sheet()
    {
        $this->firstsheet = $this->index;
    }

    /**
    * Set the worksheet protection flag to prevent accidental modification and to
    * hide formulas if the locked and hidden format properties have been set.
    *
    * @access public
    * @param string $password The password to use for protecting the sheet.
    */
    function protect($password)
    {
        $this->_protect   = 1;
        $this->_password  = $this->_encode_password($password);
    }

    /**
    * Set the width of a single column or a range of columns.
    *
    * @access public
    * @see _store_colinfo()
    * @param integer $firstcol first column on the range
    * @param integer $lastcol  last column on the range
    * @param integer $width    width to set
    * @param mixed   $format   The optional XF format to apply to the columns
    * @param integer $hidden   The optional hidden atribute
    */
    function set_column($firstcol, $lastcol, $width, $format = 0, $hidden = 0)
    {
        $this->colinfo[] = array($firstcol, $lastcol, $width, $format, $hidden);

        // Set width to zero if column is hidden
        $width = ($hidden) ? 0 : $width;

        for($col = $firstcol; $col <= $lastcol; $col++) {
            $this->col_sizes[$col] = $width;
        }
    }

    /**
    * Set which cell or cells are selected in a worksheet
    *
    * @access public
    * @param integer $first_row    first row in the selected quadrant
    * @param integer $first_column first column in the selected quadrant
    * @param integer $last_row     last row in the selected quadrant
    * @param integer $last_column  last column in the selected quadrant
    * @see _store_selection()
    */
    function set_selection($first_row,$first_column,$last_row,$last_column)
    {
        $this->_selection = array($first_row,$first_column,$last_row,$last_column);
    }

    /**
    * Set panes and mark them as frozen.
    *
    * @access public
    * @param array $panes This is the only parameter received and is composed of the following:
    *                     0 => Vertical split position,
    *                     1 => Horizontal split position
    *                     2 => Top row visible
    *                     3 => Leftmost column visible
    *                     4 => Active pane
    */
    function freeze_panes($panes)
    {
        $this->_frozen = 1;
        $this->_panes  = $panes;
    }

    /**
    * Set panes and mark them as unfrozen.
    *
    * @access public
    * @param array $panes This is the only parameter received and is composed of the following:
    *                     0 => Vertical split position,
    *                     1 => Horizontal split position
    *                     2 => Top row visible
    *                     3 => Leftmost column visible
    *                     4 => Active pane
    */
    function thaw_panes($panes)
    {
        $this->_frozen = 0;
        $this->_panes  = $panes;
    }

    /**
    * Set the page orientation as portrait.
    *
    * @access public
    */
    function set_portrait()
    {
        $this->_orientation = 1;
    }

    /**
    * Set the page orientation as landscape.
    *
    * @access public
    */
    function set_landscape()
    {
        $this->_orientation = 0;
    }

    /**
    * Set the paper type. Ex. 1 = US Letter, 9 = A4
    *
    * @access public
    * @param integer $size The type of paper size to use
    */
    function set_paper($size = 0)
    {
        $this->_paper_size = $size;
    }


    /**
    * Set the page header caption and optional margin.
    *
    * @access public
    * @param string $string The header text
    * @param float  $margin optional head margin in inches.
    */
    function set_header($string,$margin = 0.50)
    {
        if (strlen($string) >= 255) {
            //carp 'Header string must be less than 255 characters';
            return;
        }
        $this->_header      = $string;
        $this->_margin_head = $margin;
    }

    /**
    * Set the page footer caption and optional margin.
    *
    * @access public
    * @param string $string The footer text
    * @param float  $margin optional foot margin in inches.
    */
    function set_footer($string,$margin = 0.50)
    {
        if (strlen($string) >= 255) {
            //carp 'Footer string must be less than 255 characters';
            return;
        }
        $this->_footer      = $string;
        $this->_margin_foot = $margin;
    }

    /**
    * Center the page horinzontally.
    *
    * @access public
    * @param integer $center the optional value for centering. Defaults to 1 (center).
    */
    function center_horizontally($center = 1)
    {
        $this->_hcenter = $center;
    }

    /**
    * Center the page horinzontally.
    *
    * @access public
    * @param integer $center the optional value for centering. Defaults to 1 (center).
    */
    function center_vertically($center = 1)
    {
        $this->_vcenter = $center;
    }

    /**
    * Set all the page margins to the same value in inches.
    *
    * @access public
    * @param float $margin The margin to set in inches
    */
    function set_margins($margin)
    {
        $this->set_margin_left($margin);
        $this->set_margin_right($margin);
        $this->set_margin_top($margin);
        $this->set_margin_bottom($margin);
    }

    /**
    * Set the left and right margins to the same value in inches.
    *
    * @access public
    * @param float $margin The margin to set in inches
    */
    function set_margins_LR($margin)
    {
        $this->set_margin_left($margin);
        $this->set_margin_right($margin);
    }

    /**
    * Set the top and bottom margins to the same value in inches.
    *
    * @access public
    * @param float $margin The margin to set in inches
    */
    function set_margins_TB($margin)
    {
        $this->set_margin_top($margin);
        $this->set_margin_bottom($margin);
    }

    /**
    * Set the left margin in inches.
    *
    * @access public
    * @param float $margin The margin to set in inches
    */
    function set_margin_left($margin = 0.75)
    {
        $this->_margin_left = $margin;
    }

    /**
    * Set the right margin in inches.
    *
    * @access public
    * @param float $margin The margin to set in inches
    */
    function set_margin_right($margin = 0.75)
    {
        $this->_margin_right = $margin;
    }

    /**
    * Set the top margin in inches.
    *
    * @access public
    * @param float $margin The margin to set in inches
    */
    function set_margin_top($margin = 1.00)
    {
        $this->_margin_top = $margin;
    }

    /**
    * Set the bottom margin in inches.
    *
    * @access public
    * @param float $margin The margin to set in inches
    */
    function set_margin_bottom($margin = 1.00)
    {
        $this->_margin_bottom = $margin;
    }

    /**
    * Set the rows to repeat at the top of each printed page. See also the
    * _store_name_xxxx() methods in Workbook.php
    *
    * @access public
    * @param integer $first_row First row to repeat
    * @param integer $last_row  Last row to repeat. Optional.
    */
    function repeat_rows($first_row, $last_row = NULL)
    {
        $this->_title_rowmin  = $first_row;
        if(isset($last_row)) { //Second row is optional
            $this->_title_rowmax  = $last_row;
        }
        else {
            $this->_title_rowmax  = $first_row;
        }
    }

    /**
    * Set the columns to repeat at the left hand side of each printed page.
    * See also the _store_names() methods in Workbook.php
    *
    * @access public
    * @param integer $first_col First column to repeat
    * @param integer $last_col  Last column to repeat. Optional.
    */
    function repeat_columns($first_col, $last_col = NULL)
    {
        $this->_title_colmin  = $first_col;
        if(isset($last_col)) { // Second col is optional
            $this->_title_colmax  = $last_col;
        }
        else {
            $this->_title_colmax  = $first_col;
        }
    }

    /**
    * Set the area of each worksheet that will be printed.
    *
    * @access public
    * @see Workbook::_store_names()
    * @param integer $first_row First row of the area to print
    * @param integer $first_col First column of the area to print
    * @param integer $last_row  Last row of the area to print
    * @param integer $last_col  Last column of the area to print
    */
    function print_area($first_row, $first_col, $last_row, $last_col)
    {
        $this->_print_rowmin  = $first_row;
        $this->_print_colmin  = $first_col;
        $this->_print_rowmax  = $last_row;
        $this->_print_colmax  = $last_col;
    }


    /**
    * Set the option to hide gridlines on the printed page.
    *
    * @access public
    * @see _store_print_gridlines(), _store_gridset()
    */
    function hide_gridlines()
    {
        $this->_print_gridlines = 0;
    }

    /**
    * Set the option to print the row and column headers on the printed page.
    * See also the _store_print_headers() method below.
    *
    * @access public
    * @see _store_print_headers()
    * @param integer $print Whether to print the headers or not. Defaults to 1 (print).
    */
    function print_row_col_headers($print = 1)
    {
        $this->_print_headers = $print;
    }

    /**
    * Store the vertical and horizontal number of pages that will define the
    * maximum area printed. It doesn't seem to work with OpenOffice.
    *
    * @access public
    * @param  integer $width  Maximun width of printed area in pages
    * @param  integer $heigth Maximun heigth of printed area in pages
    * @see set_print_scale()
    */
    function fit_to_pages($width, $height)
    {
        $this->_fit_page      = 1;
        $this->_fit_width     = $width;
        $this->_fit_height    = $height;
    }

    /**
    * Store the horizontal page breaks on a worksheet (for printing).
    * The breaks represent the row after which the break is inserted.
    *
    * @access public
    * @param array $breaks Array containing the horizontal page breaks
    */
    function set_h_pagebreaks($breaks)
    {
        foreach($breaks as $break) {
            array_push($this->_hbreaks,$break);
        }
    }

    /**
    * Store the vertical page breaks on a worksheet (for printing).
    * The breaks represent the column after which the break is inserted.
    *
    * @access public
    * @param array $breaks Array containing the vertical page breaks
    */
    function set_v_pagebreaks($breaks)
    {
        foreach($breaks as $break) {
            array_push($this->_vbreaks,$break);
        }
    }


    /**
    * Set the worksheet zoom factor.
    *
    * @access public
    * @param integer $scale The zoom factor
    */
    function set_zoom($scale = 100)
    {
        // Confine the scale to Excel's range
        if ($scale < 10 or $scale > 400) {
            //carp "Zoom factor $scale outside range: 10 <= zoom <= 400";
            $scale = 100;
        }

        $this->_zoom = floor($scale);
    }

    /**
    * Set the scale factor for the printed page.
    * It turns off the "fit to page" option
    *
    * @access public
    * @param integer $scale The optional scale factor. Defaults to 100
    */
    function set_print_scale($scale = 100)
    {
        // Confine the scale to Excel's range
        if ($scale < 10 or $scale > 400)
        {
            // REPLACE THIS FOR A WARNING
            die("Print scale $scale outside range: 10 <= zoom <= 400");
            $scale = 100;
        }

        // Turn off "fit to page" option
        $this->_fit_page    = 0;

        $this->_print_scale = floor($scale);
    }

    /**
    * Map to the appropriate write method acording to the token recieved.
    *
    * @access public
    * @param integer $row    The row of the cell we are writing to
    * @param integer $col    The column of the cell we are writing to
    * @param mixed   $token  What we are writing
    * @param mixed   $format The optional format to apply to the cell
    */
    function write($row, $col, $token, $format = 0)
    {
        // Check for a cell reference in A1 notation and substitute row and column
        /*if ($_[0] =~ /^\D/) {
            @_ = $this->_substitute_cellref(@_);
    }*/

        /*
        # Match an array ref.
        if (ref $token eq "ARRAY") {
            return $this->write_row(@_);
    }*/

        // Match number
        if (preg_match("/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/",$token)) {
            return $this->write_number($row,$col,$token,$format);
        }
        // Match http or ftp URL
        elseif (preg_match("/^[fh]tt?p:\/\//",$token)) {
            return $this->write_url($row, $col, $token, $format);
        }
        // Match mailto:
        elseif (preg_match("/^mailto:/",$token)) {
            return $this->write_url($row, $col, $token, $format);
        }
        // Match internal or external sheet link
        elseif (preg_match("/^(?:in|ex)ternal:/",$token)) {
            return $this->write_url($row, $col, $token, $format);
        }
        // Match formula
        elseif (preg_match("/^=/",$token)) {
            return $this->write_formula($row, $col, $token, $format);
        }
        // Match formula
        elseif (preg_match("/^@/",$token)) {
            return $this->write_formula($row, $col, $token, $format);
        }
        // Match blank
        elseif ($token == '') {
            return $this->write_blank($row,$col,$format);
        }
        // Default: match string
        else {
            return $this->write_string($row,$col,$token,$format);
        }
    }

    /**
    * Returns an index to the XF record in the workbook
    *
    * @param mixed $format The optional XF format
    * @return integer The XF record index
    */
    function _XF(&$format)
    {
        if($format != 0)
        {
            return($format->get_xf_index());
        }
        else
        {
            return(0x0F);
        }
    }


    /******************************************************************************
    *******************************************************************************
    *
    * Internal methods
    */


    /**
    * Store Worksheet data in memory using the parent's class append() or to a
    * temporary file, the default.
    *
    * @param string $data The binary data to append
    */
    function _append($data)
    {
        if ($this->_using_tmpfile)
        {
            // Add CONTINUE records if necessary
            if (strlen($data) > $this->_limit) {
                $data = $this->_add_continue($data);
            }
            fwrite($this->_filehandle,$data);
            $this->_datasize += strlen($data);
        }
        else {
            parent::_append($data);
        }
    }

    /**
    * Substitute an Excel cell reference in A1 notation for  zero based row and
    * column values in an argument list.
    *
    * Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
    *
    * @param string $cell The cell reference. Or range of cells.
    * @return array
    */
    function _substitute_cellref($cell)
    {
        $cell = strtoupper($cell);

        // Convert a column range: 'A:A' or 'B:G'
        if (preg_match("/([A-I]?[A-Z]):([A-I]?[A-Z])/",$cell,$match)) {
            list($no_use, $col1) =  $this->_cell_to_rowcol($match[1] .'1'); // Add a dummy row
            list($no_use, $col2) =  $this->_cell_to_rowcol($match[2] .'1'); // Add a dummy row
            return(array($col1, $col2));
        }

        // Convert a cell range: 'A1:B7'
        if (preg_match("/\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)/",$cell,$match)) {
            list($row1, $col1) =  $this->_cell_to_rowcol($match[1]);
            list($row2, $col2) =  $this->_cell_to_rowcol($match[2]);
            return(array($row1, $col1, $row2, $col2));
        }

        // Convert a cell reference: 'A1' or 'AD2000'
        if (preg_match("/\$?([A-I]?[A-Z]\$?\d+)/",$cell)) {
            list($row1, $col1) =  $this->_cell_to_rowcol($match[1]);
            return(array($row1, $col1));
        }

        die("Unknown cell reference $cell ");
    }

    /**
    * Convert an Excel cell reference in A1 notation to a zero based row and column
    * reference; converts C1 to (0, 2).
    *
    * @param string $cell The cell reference.
    * @return array containing (row, column)
    */
    function _cell_to_rowcol($cell)
    {
        preg_match("/\$?([A-I]?[A-Z])\$?(\d+)/",$cell,$match);
        $col     = $match[1];
        $row     = $match[2];

        // Convert base26 column string to number
        $chars = explode('', $col);
        $expn  = 0;
        $col   = 0;

        while ($chars) {
            $char = array_pop($chars);        // LS char first
            $col += (ord($char) -ord('A') +1) * pow(26,$expn);
            $expn++;
        }

        // Convert 1-index to zero-index
        $row--;
        $col--;

        return(array($row, $col));
    }

    /**
    * Based on the algorithm provided by Daniel Rentz of OpenOffice.
    *
    * @param string $plaintext The password to be encoded in plaintext.
    * @return string The encoded password
    */
    function _encode_password($plaintext)
    {
        $password = 0x0000;
        $i        = 1;       // char position

        // split the plain text password in its component characters
        $chars = preg_split('//', $plaintext, -1, PREG_SPLIT_NO_EMPTY);
        foreach($chars as $char)
        {
            $value     = ord($char) << $i;   // shifted ASCII value
            $bit_16    = $value & 0x8000;    // the bit 16
            $bit_16  >>= 15;                 // 0x0000 or 0x0001
            //$bit_17    = $value & 0x00010000;
            //$bit_17  >>= 15;
            $value    &= 0x7fff;             // first 15 bits
            $password ^= ($value | $bit_16);
            //$password ^= ($value | $bit_16 | $bit_17);
            $i++;
        }

        $password ^= strlen($plaintext);
        $password ^= 0xCE4B;

        return($password);
    }

    /******************************************************************************
    *******************************************************************************
    *
    * BIFF RECORDS
    */


    /**
    * Write a double to the specified row and column (zero indexed).
    * An integer can be written as a double. Excel will display an
    * integer. $format is optional.
    *
    * Returns  0 : normal termination
    *         -2 : row or column out of range
    *
    * @access public
    * @param integer $row    Zero indexed row
    * @param integer $col    Zero indexed column
    * @param float   $num    The number to write
    * @param mixed   $format The optional XF format
    */
    function write_number($row, $col, $num, $format = 0)
    {
        $record    = 0x0203;                 // Record identifier
        $length    = 0x000E;                 // Number of bytes to follow
        $xf        = $this->_XF($format);    // The cell format

        // Check that row and col are valid and store max and min values
        if ($row >= $this->xls_rowmax)
        {
            return(-2);
        }
        if ($col >= $this->xls_colmax)
        {
            return(-2);
        }
        if ($row <  $this->dim_rowmin)
        {
            $this->dim_rowmin = $row;
        }