Subversion Repositories eFlore/Applications.cel

Rev

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

Rev 996 Rev 1604
Line 23... Line 23...
23
*/
23
*/
Line 24... Line 24...
24
 
24
 
25
/**
25
/**
26
* @const SPREADSHEET_EXCEL_WRITER_ADD token identifier for character "+"
26
* @const SPREADSHEET_EXCEL_WRITER_ADD token identifier for character "+"
27
*/
27
*/
Line 28... Line 28...
28
define('SPREADSHEET_EXCEL_WRITER_ADD',"+");
28
define('SPREADSHEET_EXCEL_WRITER_ADD', "+");
29
 
29
 
30
/**
30
/**
31
* @const SPREADSHEET_EXCEL_WRITER_SUB token identifier for character "-"
31
* @const SPREADSHEET_EXCEL_WRITER_SUB token identifier for character "-"
Line 32... Line 32...
32
*/
32
*/
33
define('SPREADSHEET_EXCEL_WRITER_SUB',"-");
33
define('SPREADSHEET_EXCEL_WRITER_SUB', "-");
34
 
34
 
35
/**
35
/**
Line 36... Line 36...
36
* @const SPREADSHEET_EXCEL_WRITER_MUL token identifier for character "*"
36
* @const SPREADSHEET_EXCEL_WRITER_MUL token identifier for character "*"
37
*/
37
*/
38
define('SPREADSHEET_EXCEL_WRITER_MUL',"*");
38
define('SPREADSHEET_EXCEL_WRITER_MUL', "*");
39
 
39
 
Line 40... Line 40...
40
/**
40
/**
41
* @const SPREADSHEET_EXCEL_WRITER_DIV token identifier for character "/"
41
* @const SPREADSHEET_EXCEL_WRITER_DIV token identifier for character "/"
42
*/
42
*/
43
define('SPREADSHEET_EXCEL_WRITER_DIV',"/");
43
define('SPREADSHEET_EXCEL_WRITER_DIV', "/");
Line 44... Line 44...
44
 
44
 
45
/**
45
/**
46
* @const SPREADSHEET_EXCEL_WRITER_OPEN token identifier for character "("
46
* @const SPREADSHEET_EXCEL_WRITER_OPEN token identifier for character "("
47
*/
47
*/
Line 48... Line 48...
48
define('SPREADSHEET_EXCEL_WRITER_OPEN',"(");
48
define('SPREADSHEET_EXCEL_WRITER_OPEN', "(");
49
 
49
 
50
/**
50
/**
51
* @const SPREADSHEET_EXCEL_WRITER_CLOSE token identifier for character ")"
51
* @const SPREADSHEET_EXCEL_WRITER_CLOSE token identifier for character ")"
-
 
52
*/
-
 
53
define('SPREADSHEET_EXCEL_WRITER_CLOSE', ")");
-
 
54
 
-
 
55
/**
-
 
56
* @const SPREADSHEET_EXCEL_WRITER_COMA token identifier for character ","
Line 52... Line 57...
52
*/
57
*/
53
define('SPREADSHEET_EXCEL_WRITER_CLOSE',")");
58
define('SPREADSHEET_EXCEL_WRITER_COMA', ",");
54
 
59
 
55
/**
60
/**
Line 56... Line 61...
56
* @const SPREADSHEET_EXCEL_WRITER_COMA token identifier for character ","
61
* @const SPREADSHEET_EXCEL_WRITER_SEMICOLON token identifier for character ";"
57
*/
62
*/
58
define('SPREADSHEET_EXCEL_WRITER_COMA',",");
63
define('SPREADSHEET_EXCEL_WRITER_SEMICOLON', ";");
59
 
64
 
Line 60... Line 65...
60
/**
65
/**
61
* @const SPREADSHEET_EXCEL_WRITER_GT token identifier for character ">"
66
* @const SPREADSHEET_EXCEL_WRITER_GT token identifier for character ">"
62
*/
67
*/
63
define('SPREADSHEET_EXCEL_WRITER_GT',">");
68
define('SPREADSHEET_EXCEL_WRITER_GT', ">");
Line 64... Line 69...
64
 
69
 
65
/**
70
/**
66
* @const SPREADSHEET_EXCEL_WRITER_LT token identifier for character "<"
71
* @const SPREADSHEET_EXCEL_WRITER_LT token identifier for character "<"
67
*/
72
*/
Line 68... Line 73...
68
define('SPREADSHEET_EXCEL_WRITER_LT',"<");
73
define('SPREADSHEET_EXCEL_WRITER_LT', "<");
69
 
74
 
70
/**
75
/**
71
* @const SPREADSHEET_EXCEL_WRITER_LE token identifier for character "<="
76
* @const SPREADSHEET_EXCEL_WRITER_LE token identifier for character "<="
Line 72... Line 77...
72
*/
77
*/
73
define('SPREADSHEET_EXCEL_WRITER_LE',"<=");
78
define('SPREADSHEET_EXCEL_WRITER_LE', "<=");
74
 
79
 
75
/**
80
/**
Line -... Line 81...
-
 
81
* @const SPREADSHEET_EXCEL_WRITER_GE token identifier for character ">="
-
 
82
*/
-
 
83
define('SPREADSHEET_EXCEL_WRITER_GE', ">=");
-
 
84
 
Line 76... Line 85...
76
* @const SPREADSHEET_EXCEL_WRITER_GE token identifier for character ">="
85
/**
Line 77... Line 86...
77
*/
86
* @const SPREADSHEET_EXCEL_WRITER_EQ token identifier for character "="
78
define('SPREADSHEET_EXCEL_WRITER_GE',">=");
87
*/
79
 
88
define('SPREADSHEET_EXCEL_WRITER_EQ', "=");
80
/**
89
 
Line 135... Line 144...
135
    * @var integer
144
    * @var integer
136
    */
145
    */
137
    var $_byte_order;
146
    var $_byte_order;
Line 138... Line 147...
138
 
147
 
139
    /**
148
    /**
140
    * Number of arguments for the current function
149
    * Array of external sheets
141
    * @var integer
150
    * @var array
142
    */
151
    */
Line 143... Line 152...
143
    var $_func_args;
152
    var $_ext_sheets;
144
 
153
 
145
    /**
154
    /**
146
    * Array of external sheets
155
    * Array of sheet references in the form of REF structures
147
    * @var array
156
    * @var array
-
 
157
    */
-
 
158
    var $_references;
-
 
159
 
-
 
160
    /**
-
 
161
    * The BIFF version for the workbook
-
 
162
    * @var integer
Line 148... Line 163...
148
    */
163
    */
149
    var $_ext_sheets;
164
    var $_BIFF_version;
150
 
165
 
151
    /**
166
    /**
152
    * The class constructor
167
    * The class constructor
153
    *
168
    *
154
    * @param integer $byte_order The byte order (Little endian or Big endian) of the architecture
169
    * @param integer $byte_order The byte order (Little endian or Big endian) of the architecture
155
                                 (optional). 1 => big endian, 0 (default) => little endian. 
170
                                 (optional). 1 => big endian, 0 (default) little endian.
156
    */
171
    */
-
 
172
    function Spreadsheet_Excel_Writer_Parser($byte_order, $biff_version)
157
    function Spreadsheet_Excel_Writer_Parser($byte_order = 0)
173
    {
158
    {
174
        $this->_current_char  = 0;
159
        $this->_current_char  = 0;
175
        $this->_BIFF_version  = $biff_version;
160
        $this->_current_token = '';       // The token we are working on.
176
        $this->_current_token = '';       // The token we are working on.
161
        $this->_formula       = "";       // The formula to parse.
177
        $this->_formula       = '';       // The formula to parse.
162
        $this->_lookahead     = '';       // The character ahead of the current char.
178
        $this->_lookahead     = '';       // The character ahead of the current char.
163
        $this->_parse_tree    = '';       // The parse tree to be generated.
-
 
164
        $this->_initializeHashes();      // Initialize the hashes: ptg's and function's ptg's
179
        $this->_parse_tree    = '';       // The parse tree to be generated.
-
 
180
        $this->_initializeHashes();      // Initialize the hashes: ptg's and function's ptg's
165
        $this->_byte_order = $byte_order; // Little Endian or Big Endian
181
        $this->_byte_order = $byte_order; // Little Endian or Big Endian
166
        $this->_func_args  = 0;           // Number of arguments for the current function
182
        $this->_ext_sheets = array();
167
        $this->_ext_sheets = array();
183
        $this->_references = array();
168
    }
184
    }
169
    
185
 
170
    /**
186
    /**
171
    * Initialize the ptg and function hashes. 
187
    * Initialize the ptg and function hashes.
172
    *
188
    *
173
    * @access private
189
    * @access private
Line 270... Line 286...
270
            'ptgRef3dA'    => 0x7A,
286
            'ptgRef3dA'    => 0x7A,
271
            'ptgArea3dA'   => 0x7B,
287
            'ptgArea3dA'   => 0x7B,
272
            'ptgRefErr3dA' => 0x7C,
288
            'ptgRefErr3dA' => 0x7C,
273
            'ptgAreaErr3d' => 0x7D
289
            'ptgAreaErr3d' => 0x7D
274
            );
290
            );
275
    
291
 
276
        // Thanks to Michael Meeks and Gnumeric for the initial arg values.
292
        // Thanks to Michael Meeks and Gnumeric for the initial arg values.
277
        //
293
        //
278
        // The following hash was generated by "function_locale.pl" in the distro.
294
        // The following hash was generated by "function_locale.pl" in the distro.
279
        // Refer to function_locale.pl for non-English function names.
295
        // Refer to function_locale.pl for non-English function names.
280
        //
296
        //
Line 513... Line 529...
513
              'COUNTIF'         => array( 346,    2,    0,    0 ),
529
              'COUNTIF'         => array( 346,    2,    0,    0 ),
514
              'COUNTBLANK'      => array( 347,    1,    0,    0 ),
530
              'COUNTBLANK'      => array( 347,    1,    0,    0 ),
515
              'ROMAN'           => array( 354,   -1,    1,    0 )
531
              'ROMAN'           => array( 354,   -1,    1,    0 )
516
              );
532
              );
517
    }
533
    }
518
    
534
 
519
    /**
535
    /**
520
    * Convert a token to the proper ptg value.
536
    * Convert a token to the proper ptg value.
521
    *
537
    *
522
    * @access private
538
    * @access private
523
    * @param mixed $token The token to convert.
539
    * @param mixed $token The token to convert.
-
 
540
    * @return mixed the converted token on success. PEAR_Error if the token
-
 
541
    *               is not recognized
524
    */
542
    */
525
    function _convert($token)
543
    function _convert($token)
526
    {
544
    {
527
        if (preg_match("/^\"[^\"]{0,255}\"$/", $token))
545
        if (preg_match("/^\"[^\"]{0,255}\"$/", $token)) {
528
        {
-
 
529
            return $this->_convertString($token);
546
            return $this->_convertString($token);
530
        }
547
 
531
        elseif (is_numeric($token))
548
        } elseif (is_numeric($token)) {
532
        {
-
 
533
            return $this->_convertNumber($token);
549
            return $this->_convertNumber($token);
534
        }
550
 
535
        // match references like A1 or $A$1
551
        // match references like A1 or $A$1
536
        elseif(preg_match('/^\$?([A-I]?[A-Z])\$?(\d+)$/',$token))
552
        } elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/',$token)) {
537
        {
-
 
538
            return($this->_convertRef2d($token));
553
            return $this->_convertRef2d($token);
539
        }
554
 
540
        // match external references like Sheet1:Sheet2!A1
555
        // match external references like Sheet1!A1 or Sheet1:Sheet2!A1
541
        elseif (preg_match("/^[A-Za-z0-9_]+(\:[A-Za-z0-9_]+)?\![A-I]?[A-Z](\d+)$/",$token))
556
        } elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z](\d+)$/u",$token)) {
542
        {
-
 
543
            return $this->_convertRef3d($token);
557
            return $this->_convertRef3d($token);
-
 
558
 
-
 
559
        // match external references like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1
-
 
560
        } elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z](\d+)$/u",$token)) {
544
        }
561
            return $this->_convertRef3d($token);
-
 
562
 
545
        // match ranges like A1:B2
563
        // match ranges like A1:B2
546
        elseif(preg_match("/^(\$)?[A-I]?[A-Z](\$)?(\d+)\:(\$)?[A-I]?[A-Z](\$)?(\d+)$/",$token))
564
        } elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token)) {
547
        {
-
 
548
            return($this->_convertRange2d($token));
565
            return $this->_convertRange2d($token);
549
        }
566
 
550
        // match ranges like A1..B2
567
        // match ranges like A1..B2
551
        elseif(preg_match("/^(\$)?[A-I]?[A-Z](\$)?(\d+)\.\.(\$)?[A-I]?[A-Z](\$)?(\d+)$/",$token))
568
        } elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token)) {
552
        {
-
 
553
            return($this->_convertRange2d($token));
569
            return $this->_convertRange2d($token);
554
        }
570
 
555
        // match external ranges like Sheet1:Sheet2!A1:B2
571
        // match external ranges like Sheet1!A1 or Sheet1:Sheet2!A1:B2
556
        elseif (preg_match("/^[A-Za-z0-9_]+(\:[A-Za-z0-9_]+)?\!([A-I]?[A-Z])?(\d+)\:([A-I]?[A-Z])?(\d+)$/",$token))
572
        } elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/u",$token)) {
557
        {
-
 
558
            return $this->_convertRange3d($token);
573
            return $this->_convertRange3d($token);
-
 
574
 
-
 
575
        // match external ranges like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
-
 
576
        } elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/u",$token)) {
559
        }
577
            return $this->_convertRange3d($token);
-
 
578
 
560
        elseif(isset($this->ptg[$token])) // operators (including parentheses)
579
        // operators (including parentheses)
561
        {
580
        } elseif (isset($this->ptg[$token])) {
562
            return(pack("C", $this->ptg[$token]));
581
            return pack("C", $this->ptg[$token]);
563
        }
582
 
-
 
583
        // commented so argument number can be processed correctly. See toReversePolish().
564
        elseif(preg_match("/[A-Z0-9\xc0-\xdc\.]+/",$token))
584
        /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/",$token))
565
        {
585
        {
566
            return($this->_convertFunction($token,$this->_func_args));
586
            return($this->_convertFunction($token,$this->_func_args));
567
        }
587
        }*/
-
 
588
 
568
        // if it's an argument, ignore the token (the argument remains)
589
        // if it's an argument, ignore the token (the argument remains)
569
        elseif($token == 'arg')
590
        } elseif ($token == 'arg') {
570
        {
-
 
571
            $this->_func_args++;
-
 
572
            return('');
591
            return '';
573
        }
592
        }
574
        // TODO: use real error codes
593
        // TODO: use real error codes
575
        $this->raiseError("Unknown token $token", 0, PEAR_ERROR_DIE);
594
        return $this->raiseError("Unknown token $token");
576
    }
595
    }
577
    
596
 
578
    /**
597
    /**
579
    * Convert a number token to ptgInt or ptgNum
598
    * Convert a number token to ptgInt or ptgNum
580
    *
599
    *
581
    * @access private
600
    * @access private
582
    * @param mixed $num an integer or double for conversion to its ptg value
601
    * @param mixed $num an integer or double for conversion to its ptg value
583
    */
602
    */
584
    function _convertNumber($num)
603
    function _convertNumber($num)
585
    {
604
    {
586
        // Integer in the range 0..2**16-1
605
        // Integer in the range 0..2**16-1
587
        if ((preg_match("/^\d+$/",$num)) and ($num <= 65535)) {
606
        if ((preg_match("/^\d+$/", $num)) and ($num <= 65535)) {
588
            return(pack("Cv", $this->ptg['ptgInt'], $num));
607
            return pack("Cv", $this->ptg['ptgInt'], $num);
589
        }
-
 
590
        else // A float
608
        } else { // A float
591
        {
-
 
592
            if($this->_byte_order) // if it's Big Endian
609
            if ($this->_byte_order) { // if it's Big Endian
593
            {
-
 
594
                $num = strrev($num);
610
                $num = strrev($num);
595
            }
611
            }
596
            return(pack("Cd", $this->ptg['ptgNum'], $num));
612
            return pack("Cd", $this->ptg['ptgNum'], $num);
597
        }
613
        }
598
    }
614
    }
599
    
615
 
600
    /**
616
    /**
601
    * Convert a string token to ptgStr
617
    * Convert a string token to ptgStr
602
    *
618
    *
603
    * @access private
619
    * @access private
604
    * @param string $string A string for conversion to its ptg value
620
    * @param string $string A string for conversion to its ptg value.
-
 
621
    * @return mixed the converted token on success. PEAR_Error if the string
-
 
622
    *               is longer than 255 characters.
605
    */
623
    */
606
    function _convertString($string)
624
    function _convertString($string)
607
    {
625
    {
608
        // chop away beggining and ending quotes
626
        // chop away beggining and ending quotes
609
        $string = substr($string, 1, strlen($string) - 2);
627
        $string = substr($string, 1, strlen($string) - 2);
-
 
628
        if (strlen($string) > 255) {
-
 
629
            return $this->raiseError("String is too long");
-
 
630
        }
-
 
631
 
-
 
632
        if ($this->_BIFF_version == 0x0500) {
610
        return pack("CC", $this->ptg['ptgStr'], strlen($string)).$string;
633
            return pack("CC", $this->ptg['ptgStr'], strlen($string)).$string;
-
 
634
        } elseif ($this->_BIFF_version == 0x0600) {
-
 
635
            $encoding = 0;   // TODO: Unicode support
-
 
636
            return pack("CCC", $this->ptg['ptgStr'], strlen($string), $encoding).$string;
-
 
637
        }
611
    }
638
    }
612
 
639
 
613
    /**
640
    /**
614
    * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
641
    * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
615
    * args that it takes.
642
    * args that it takes.
616
    *
643
    *
617
    * @access private
644
    * @access private
618
    * @param string  $token    The name of the function for convertion to ptg value.
645
    * @param string  $token    The name of the function for convertion to ptg value.
619
    * @param integer $num_args The number of arguments the function recieves.
646
    * @param integer $num_args The number of arguments the function receives.
-
 
647
    * @return string The packed ptg for the function
620
    */
648
    */
621
    function _convertFunction($token, $num_args)
649
    function _convertFunction($token, $num_args)
622
    {
650
    {
623
        $this->_func_args = 0; // re initialize the number of arguments
-
 
624
        $args     = $this->_functions[$token][1];
651
        $args     = $this->_functions[$token][1];
625
        $volatile = $this->_functions[$token][3];
652
        $volatile = $this->_functions[$token][3];
626
    
653
 
627
        // Fixed number of args eg. TIME($i,$j,$k).
654
        // Fixed number of args eg. TIME($i,$j,$k).
628
        if ($args >= 0) {
655
        if ($args >= 0) {
629
            return(pack("Cv", $this->ptg['ptgFuncV'], $this->_functions[$token][0]));
656
            return pack("Cv", $this->ptg['ptgFuncV'], $this->_functions[$token][0]);
630
        }
657
        }
631
        // Variable number of args eg. SUM($i,$j,$k, ..).
658
        // Variable number of args eg. SUM($i,$j,$k, ..).
632
        if ($args == -1) {
659
        if ($args == -1) {
633
            return(pack("CCv", $this->ptg['ptgFuncVarV'], $num_args, $this->_functions[$token][0]));
660
            return pack("CCv", $this->ptg['ptgFuncVarV'], $num_args, $this->_functions[$token][0]);
634
        }
661
        }
635
    }
662
    }
636
    
663
 
637
    /**
664
    /**
638
    * Convert an Excel range such as A1:D4 to a ptgRefV.
665
    * Convert an Excel range such as A1:D4 to a ptgRefV.
639
    *
666
    *
640
    * @access private
667
    * @access private
641
    * @param string $range An Excel range in the A1:A2 or A1..A2 format.
668
    * @param string $range An Excel range in the A1:A2 or A1..A2 format.
642
    */
669
    */
643
    function _convertRange2d($range)
670
    function _convertRange2d($range, $class=0)
644
    {
671
    {
645
        $class = 2; // as far as I know, this is magick.
-
 
646
    
672
 
-
 
673
        // TODO: possible class value 0,1,2 check Formula.pm
647
        // Split the range into 2 cell refs
674
        // Split the range into 2 cell refs
648
        if(preg_match("/^([A-I]?[A-Z])(\d+)\:([A-I]?[A-Z])(\d+)$/",$range)) {
675
        if (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\:([A-Ia-i]?[A-Za-z])(\d+)$/", $range)) {
649
            list($cell1, $cell2) = explode(':', $range);
676
            list($cell1, $cell2) = explode(':', $range);
650
        }
-
 
651
        elseif(preg_match("/^([A-I]?[A-Z])(\d+)\.\.([A-I]?[A-Z])(\d+)$/",$range)) {
677
        } elseif (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\.\.([A-Ia-i]?[A-Za-z])(\d+)$/", $range)) {
652
            list($cell1, $cell2) = explode('\.\.', $range);
678
            list($cell1, $cell2) = explode('..', $range);
653
        
679
 
654
        }
-
 
655
        else {
680
        } else {
656
            // TODO: use real error codes
681
            // TODO: use real error codes
657
            $this->raiseError("Unknown range separator", 0, PEAR_ERROR_DIE);
682
            return $this->raiseError("Unknown range separator", 0, PEAR_ERROR_DIE);
658
        }
683
        }
659
    
684
 
660
        // Convert the cell references
685
        // Convert the cell references
661
        $cell_array1 = $this->_cellToPackedRowcol($cell1);
686
        $cell_array1 = $this->_cellToPackedRowcol($cell1);
662
        if($this->isError($cell_array1)) {
687
        if (PEAR::isError($cell_array1)) {
663
            return($cell_array1);
688
            return $cell_array1;
664
            }
689
        }
665
        list($row1, $col1) = $cell_array1; //$this->_cellToPackedRowcol($cell1);
690
        list($row1, $col1) = $cell_array1;
666
        $cell_array2 = $this->_cellToPackedRowcol($cell2);
691
        $cell_array2 = $this->_cellToPackedRowcol($cell2);
667
        if($this->isError($cell_array2)) {
692
        if (PEAR::isError($cell_array2)) {
668
            return($cell_array2);
693
            return $cell_array2;
669
            }
694
        }
670
        list($row2, $col2) = $cell_array2; //$this->_cellToPackedRowcol($cell2);
695
        list($row2, $col2) = $cell_array2;
671
    
696
 
672
        // The ptg value depends on the class of the ptg.
697
        // The ptg value depends on the class of the ptg.
673
        if ($class == 0) {
698
        if ($class == 0) {
674
            $ptgArea = pack("C", $this->ptg['ptgArea']);
699
            $ptgArea = pack("C", $this->ptg['ptgArea']);
675
        }
-
 
676
        elseif ($class == 1) {
700
        } elseif ($class == 1) {
677
            $ptgArea = pack("C", $this->ptg['ptgAreaV']);
701
            $ptgArea = pack("C", $this->ptg['ptgAreaV']);
678
        }
-
 
679
        elseif ($class == 2) {
702
        } elseif ($class == 2) {
680
            $ptgArea = pack("C", $this->ptg['ptgAreaA']);
703
            $ptgArea = pack("C", $this->ptg['ptgAreaA']);
681
        }
-
 
682
        else {
704
        } else {
683
            // TODO: use real error codes
705
            // TODO: use real error codes
684
            $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
706
            return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
685
        }
707
        }
686
        return($ptgArea . $row1 . $row2 . $col1. $col2);
708
        return $ptgArea . $row1 . $row2 . $col1. $col2;
687
    }
709
    }
688
 
710
 
689
    /**
711
    /**
690
    * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
712
    * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
691
    * a ptgArea3dV.
713
    * a ptgArea3d.
692
    *
714
    *
693
    * @access private
715
    * @access private
694
    * @param string $token An Excel range in the Sheet1!A1:A2 format.
716
    * @param string $token An Excel range in the Sheet1!A1:A2 format.
-
 
717
    * @return mixed The packed ptgArea3d token on success, PEAR_Error on failure.
695
    */
718
    */
696
    function _convertRange3d($token)
719
    function _convertRange3d($token)
697
    {
720
    {
698
        $class = 2; // as far as I know, this is magick.
721
        $class = 2; // as far as I know, this is magick.
699
 
722
 
700
        // Split the ref at the ! symbol
723
        // Split the ref at the ! symbol
701
        list($ext_ref, $range) = explode('!', $token);
724
        list($ext_ref, $range) = explode('!', $token);
702
 
725
 
703
        // Convert the external reference part
726
        // Convert the external reference part (different for BIFF8)
-
 
727
        if ($this->_BIFF_version == 0x0500) {
704
        $ext_ref = $this->_packExtRef($ext_ref);
728
            $ext_ref = $this->_packExtRef($ext_ref);
705
        if ($this->isError($ext_ref)) {
729
            if (PEAR::isError($ext_ref)) {
706
            return $ext_ref;
730
                return $ext_ref;
-
 
731
            }
-
 
732
        } elseif ($this->_BIFF_version == 0x0600) {
-
 
733
             $ext_ref = $this->_getRefIndex($ext_ref);
-
 
734
             if (PEAR::isError($ext_ref)) {
-
 
735
                 return $ext_ref;
-
 
736
             }
707
        }
737
        }
708
 
738
 
709
        // Split the range into 2 cell refs
739
        // Split the range into 2 cell refs
710
        list($cell1, $cell2) = explode(':', $range);
740
        list($cell1, $cell2) = explode(':', $range);
711
 
741
 
712
        // Convert the cell references
742
        // Convert the cell references
713
        if (preg_match("/^(\$)?[A-I]?[A-Z](\$)?(\d+)$/", $cell1))
743
        if (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/", $cell1)) {
714
        {
-
 
715
            $cell_array1 = $this->_cellToPackedRowcol($cell1);
744
            $cell_array1 = $this->_cellToPackedRowcol($cell1);
716
            if (PEAR::isError($cell_array1)) {
745
            if (PEAR::isError($cell_array1)) {
717
                return $cell_array1;
746
                return $cell_array1;
718
            }
747
            }
719
            list($row1, $col1) = $cell_array1;
748
            list($row1, $col1) = $cell_array1;
720
            $cell_array2 = $this->_cellToPackedRowcol($cell2);
749
            $cell_array2 = $this->_cellToPackedRowcol($cell2);
721
            if (PEAR::isError($cell_array2)) {
750
            if (PEAR::isError($cell_array2)) {
722
                return $cell_array2;
751
                return $cell_array2;
723
            }
752
            }
724
            list($row2, $col2) = $cell_array2;
753
            list($row2, $col2) = $cell_array2;
725
        }
-
 
726
        else { // It's a columns range (like 26:27)
754
        } else { // It's a rows range (like 26:27)
727
             $cells_array = $this->_rangeToPackedRange($cell1.':'.$cell2);
755
             $cells_array = $this->_rangeToPackedRange($cell1.':'.$cell2);
728
             if (PEAR::isError($cells_array)) {
756
             if (PEAR::isError($cells_array)) {
729
                 return $cells_array;
757
                 return $cells_array;
730
             }
758
             }
731
             list($row1, $col1, $row2, $col2) = $cells_array;
759
             list($row1, $col1, $row2, $col2) = $cells_array;
732
        }
760
        }
733
 
761
 
734
        // The ptg value depends on the class of the ptg.
762
        // The ptg value depends on the class of the ptg.
735
        if ($class == 0) {
763
        if ($class == 0) {
736
            $ptgArea = pack("C", $this->ptg['ptgArea3d']);
764
            $ptgArea = pack("C", $this->ptg['ptgArea3d']);
737
        }
-
 
738
        elseif ($class == 1) {
765
        } elseif ($class == 1) {
739
            $ptgArea = pack("C", $this->ptg['ptgArea3dV']);
766
            $ptgArea = pack("C", $this->ptg['ptgArea3dV']);
740
        }
-
 
741
        elseif ($class == 2) {
767
        } elseif ($class == 2) {
742
            $ptgArea = pack("C", $this->ptg['ptgArea3dA']);
768
            $ptgArea = pack("C", $this->ptg['ptgArea3dA']);
-
 
769
        } else {
-
 
770
            return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
743
        }
771
        }
744
        else {
-
 
745
            $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
-
 
746
        }
-
 
747
 
772
 
748
        return $ptgArea . $ext_ref . $row1 . $row2 . $col1. $col2;
773
        return $ptgArea . $ext_ref . $row1 . $row2 . $col1. $col2;
749
    }
774
    }
Line 750... Line 775...
750
 
775
 
751
    /**
776
    /**
Line 756... Line 781...
756
    * @return string The cell in packed() format with the corresponding ptg
781
    * @return string The cell in packed() format with the corresponding ptg
757
    */
782
    */
758
    function _convertRef2d($cell)
783
    function _convertRef2d($cell)
759
    {
784
    {
760
        $class = 2; // as far as I know, this is magick.
785
        $class = 2; // as far as I know, this is magick.
761
    
786
 
762
        // Convert the cell reference
787
        // Convert the cell reference
763
        $cell_array = $this->_cellToPackedRowcol($cell);
788
        $cell_array = $this->_cellToPackedRowcol($cell);
764
        if($this->isError($cell_array)) {
789
        if (PEAR::isError($cell_array)) {
765
            return($cell_array);
790
            return $cell_array;
766
            }
791
        }
767
        list($row, $col) = $cell_array;
792
        list($row, $col) = $cell_array;
768
    
793
 
769
        // The ptg value depends on the class of the ptg.
794
        // The ptg value depends on the class of the ptg.
770
        if ($class == 0) {
795
        if ($class == 0) {
771
            $ptgRef = pack("C", $this->ptg['ptgRef']);
796
            $ptgRef = pack("C", $this->ptg['ptgRef']);
772
        }
-
 
773
        elseif ($class == 1) {
797
        } elseif ($class == 1) {
774
            $ptgRef = pack("C", $this->ptg['ptgRefV']);
798
            $ptgRef = pack("C", $this->ptg['ptgRefV']);
775
        }
-
 
776
        elseif ($class == 2) {
799
        } elseif ($class == 2) {
777
            $ptgRef = pack("C", $this->ptg['ptgRefA']);
800
            $ptgRef = pack("C", $this->ptg['ptgRefA']);
778
        }
-
 
779
        else {
801
        } else {
780
            // TODO: use real error codes
802
            // TODO: use real error codes
781
            $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
803
            return $this->raiseError("Unknown class $class");
782
        }
804
        }
783
        return($ptgRef.$row.$col);
805
        return $ptgRef.$row.$col;
784
    }
806
    }
785
    
807
 
786
    /**
808
    /**
787
    * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
809
    * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
788
    * ptgRef3dV.
810
    * ptgRef3d.
789
    *
811
    *
790
    * @access private
812
    * @access private
791
    * @param string $cell An Excel cell reference
813
    * @param string $cell An Excel cell reference
792
    * @return string The cell in packed() format with the corresponding ptg
814
    * @return mixed The packed ptgRef3d token on success, PEAR_Error on failure.
793
    */
815
    */
794
    function _convertRef3d($cell)
816
    function _convertRef3d($cell)
795
    {
817
    {
796
        $class = 2; // as far as I know, this is magick.
818
        $class = 2; // as far as I know, this is magick.
797
 
819
 
798
        // Split the ref at the ! symbol
820
        // Split the ref at the ! symbol
799
        list($ext_ref, $cell) = explode('!', $cell);
821
        list($ext_ref, $cell) = explode('!', $cell);
800
 
822
 
801
        // Convert the external reference part
823
        // Convert the external reference part (different for BIFF8)
-
 
824
        if ($this->_BIFF_version == 0x0500) {
802
        $ext_ref = $this->_packExtRef($ext_ref);
825
            $ext_ref = $this->_packExtRef($ext_ref);
-
 
826
            if (PEAR::isError($ext_ref)) {
-
 
827
                return $ext_ref;
-
 
828
            }
-
 
829
        } elseif ($this->_BIFF_version == 0x0600) {
-
 
830
            $ext_ref = $this->_getRefIndex($ext_ref);
803
        if ($this->isError($ext_ref)) {
831
            if (PEAR::isError($ext_ref)) {
804
            return $ext_ref;
832
                return $ext_ref;
-
 
833
            }
805
        }
834
        }
806
 
835
 
807
        // Convert the cell reference part
836
        // Convert the cell reference part
808
        list($row, $col) = $this->_cellToPackedRowcol($cell);
837
        list($row, $col) = $this->_cellToPackedRowcol($cell);
809
 
838
 
810
        // The ptg value depends on the class of the ptg.
839
        // The ptg value depends on the class of the ptg.
811
        if ($class == 0) {
840
        if ($class == 0) {
812
            $ptgRef = pack("C", $this->ptg['ptgRef3d']);
841
            $ptgRef = pack("C", $this->ptg['ptgRef3d']);
813
        }
-
 
814
        elseif ($class == 1) {
842
        } elseif ($class == 1) {
815
            $ptgRef = pack("C", $this->ptg['ptgRef3dV']);
843
            $ptgRef = pack("C", $this->ptg['ptgRef3dV']);
816
        }
-
 
817
        elseif ($class == 2) {
844
        } elseif ($class == 2) {
818
            $ptgRef = pack("C", $this->ptg['ptgRef3dA']);
845
            $ptgRef = pack("C", $this->ptg['ptgRef3dA']);
819
        }
-
 
820
        else {
846
        } else {
821
            $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
847
            return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
822
        }
848
        }
Line 823... Line 849...
823
 
849
 
824
        return $ptgRef . $ext_ref. $row . $col;
850
        return $ptgRef . $ext_ref. $row . $col;
Line 834... Line 860...
834
    */
860
    */
835
    function _packExtRef($ext_ref)
861
    function _packExtRef($ext_ref)
836
    {
862
    {
837
        $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
863
        $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
838
        $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
864
        $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
839
 
865
 
840
        // Check if there is a sheet range eg., Sheet1:Sheet2.
866
        // Check if there is a sheet range eg., Sheet1:Sheet2.
841
        if (preg_match("/:/", $ext_ref))
867
        if (preg_match("/:/", $ext_ref)) {
842
        {
-
 
843
            list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
868
            list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
844
 
869
 
845
            $sheet1 = $this->_getSheetIndex($sheet_name1);
870
            $sheet1 = $this->_getSheetIndex($sheet_name1);
846
            if ($sheet1 == -1) {
871
            if ($sheet1 == -1) {
847
                return $this->raiseError("Unknown sheet name $sheet_name1 in formula");
872
                return $this->raiseError("Unknown sheet name $sheet_name1 in formula");
848
            }
873
            }
849
            $sheet2 = $this->_getSheetIndex($sheet_name2);
874
            $sheet2 = $this->_getSheetIndex($sheet_name2);
850
            if ($sheet2 == -1) {
875
            if ($sheet2 == -1) {
851
                return $this->raiseError("Unknown sheet name $sheet_name2 in formula");
876
                return $this->raiseError("Unknown sheet name $sheet_name2 in formula");
852
            }
877
            }
853
 
878
 
854
            // Reverse max and min sheet numbers if necessary
879
            // Reverse max and min sheet numbers if necessary
855
            if ($sheet1 > $sheet2) {
880
            if ($sheet1 > $sheet2) {
856
                list($sheet1, $sheet2) = array($sheet2, $sheet1);
881
                list($sheet1, $sheet2) = array($sheet2, $sheet1);
857
            }
882
            }
858
        }
-
 
859
        else // Single sheet name only.
883
        } else { // Single sheet name only.
860
        {
-
 
861
            $sheet1 = $this->_getSheetIndex($ext_ref);
884
            $sheet1 = $this->_getSheetIndex($ext_ref);
862
            if ($sheet1 == -1) {
885
            if ($sheet1 == -1) {
863
                return $this->raiseError("Unknown sheet name $ext_ref in formula");
886
                return $this->raiseError("Unknown sheet name $ext_ref in formula");
864
            }
887
            }
865
            $sheet2 = $sheet1;
888
            $sheet2 = $sheet1;
866
        }
889
        }
867
 
890
 
868
        // References are stored relative to 0xFFFF.
891
        // References are stored relative to 0xFFFF.
869
        $offset = -1 - $sheet1;
892
        $offset = -1 - $sheet1;
Line 870... Line 893...
870
 
893
 
871
        return pack('vdvv', $offset, 0x00, $sheet1, $sheet2);
894
        return pack('vdvv', $offset, 0x00, $sheet1, $sheet2);
Line 872... Line 895...
872
    }
895
    }
-
 
896
 
-
 
897
    /**
-
 
898
    * Look up the REF index that corresponds to an external sheet name
-
 
899
    * (or range). If it doesn't exist yet add it to the workbook's references
-
 
900
    * array. It assumes all sheet names given must exist.
-
 
901
    *
-
 
902
    * @access private
-
 
903
    * @param string $ext_ref The name of the external reference
-
 
904
    * @return mixed The reference index in packed() format on success,
-
 
905
    *               PEAR_Error on failure
-
 
906
    */
-
 
907
    function _getRefIndex($ext_ref)
-
 
908
    {
-
 
909
        $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
-
 
910
        $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
-
 
911
 
-
 
912
        // Check if there is a sheet range eg., Sheet1:Sheet2.
-
 
913
        if (preg_match("/:/", $ext_ref)) {
-
 
914
            list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
-
 
915
 
-
 
916
            $sheet1 = $this->_getSheetIndex($sheet_name1);
-
 
917
            if ($sheet1 == -1) {
-
 
918
                return $this->raiseError("Unknown sheet name $sheet_name1 in formula");
-
 
919
            }
-
 
920
            $sheet2 = $this->_getSheetIndex($sheet_name2);
-
 
921
            if ($sheet2 == -1) {
-
 
922
                return $this->raiseError("Unknown sheet name $sheet_name2 in formula");
-
 
923
            }
-
 
924
 
-
 
925
            // Reverse max and min sheet numbers if necessary
-
 
926
            if ($sheet1 > $sheet2) {
-
 
927
                list($sheet1, $sheet2) = array($sheet2, $sheet1);
-
 
928
            }
-
 
929
        } else { // Single sheet name only.
-
 
930
            $sheet1 = $this->_getSheetIndex($ext_ref);
-
 
931
            if ($sheet1 == -1) {
-
 
932
                return $this->raiseError("Unknown sheet name $ext_ref in formula");
-
 
933
            }
-
 
934
            $sheet2 = $sheet1;
-
 
935
        }
-
 
936
 
-
 
937
        // assume all references belong to this document
-
 
938
        $supbook_index = 0x00;
-
 
939
        $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
-
 
940
        $total_references = count($this->_references);
-
 
941
        $index = -1;
-
 
942
        for ($i = 0; $i < $total_references; $i++) {
-
 
943
            if ($ref == $this->_references[$i]) {
-
 
944
                $index = $i;
-
 
945
                break;
-
 
946
            }
-
 
947
        }
-
 
948
        // if REF was not found add it to references array
-
 
949
        if ($index == -1) {
-
 
950
            $this->_references[$total_references] = $ref;
-
 
951
            $index = $total_references;
-
 
952
        }
-
 
953
 
-
 
954
        return pack('v', $index);
-
 
955
    }
873
 
956
 
874
    /**
957
    /**
875
    * Look up the index that corresponds to an external sheet name. The hash of
958
    * Look up the index that corresponds to an external sheet name. The hash of
876
    * sheet names is updated by the addworksheet() method of the 
959
    * sheet names is updated by the addworksheet() method of the
877
    * Spreadsheet_Excel_Writer_Workbook class.
960
    * Spreadsheet_Excel_Writer_Workbook class.
878
    *
961
    *
879
    * @access private
962
    * @access private
880
    * @return integer
963
    * @return integer The sheet index, -1 if the sheet was not found
881
    */
964
    */
882
    function _getSheetIndex($sheet_name)
965
    function _getSheetIndex($sheet_name)
883
    {
966
    {
884
        if (!isset($this->_ext_sheets[$sheet_name])) {
-
 
885
            return -1;
967
        if (!isset($this->_ext_sheets[$sheet_name])) {
886
        }
968
            return -1;
887
        else {
969
        } else {
888
            return $this->_ext_sheets[$sheet_name];
970
            return $this->_ext_sheets[$sheet_name];
Line 889... Line 971...
889
        }
971
        }
890
    }
972
    }
891
 
973
 
-
 
974
    /**
892
    /**
975
    * This method is used to update the array of sheet names. It is
893
    * This method is used to update the array of sheet names. It is
976
    * called by the addWorksheet() method of the
-
 
977
    * Spreadsheet_Excel_Writer_Workbook class.
894
    * called by the addWorksheet() method of the Spreadsheet_Excel_Writer_Workbook class.
978
    *
895
    *
979
    * @access public
896
    * @access private
980
    * @see Spreadsheet_Excel_Writer_Workbook::addWorksheet()
897
    * @param string  $name  The name of the worksheet being added
981
    * @param string  $name  The name of the worksheet being added
898
    * @param integer $index The index of the worksheet being added
982
    * @param integer $index The index of the worksheet being added
899
    */
983
    */
900
    function setExtSheet($name, $index)
984
    function setExtSheet($name, $index)
Line 901... Line 985...
901
    {
985
    {
902
        $this->_ext_sheets[$name] = $index;
986
        $this->_ext_sheets[$name] = $index;
903
    }
987
    }
904
 
988
 
905
    /**
989
    /**
906
    * pack() row and column into the required 3 byte format.
990
    * pack() row and column into the required 3 or 4 byte format.
907
    *
991
    *
908
    * @access private
992
    * @access private
909
    * @param string $cell The Excel cell reference to be packed
993
    * @param string $cell The Excel cell reference to be packed
-
 
994
    * @return array Array containing the row and column in packed() format
910
    * @return array Array containing the row and column in packed() format
995
    */
911
    */
996
    function _cellToPackedRowcol($cell)
912
    function _cellToPackedRowcol($cell)
997
    {
913
    {
998
        $cell = strtoupper($cell);
-
 
999
        list($row, $col, $row_rel, $col_rel) = $this->_cellToRowcol($cell);
914
        list($row, $col, $row_rel, $col_rel) = $this->_cellToRowcol($cell);
1000
        if ($col >= 256) {
915
        if ($col >= 256) {
1001
            return $this->raiseError("Column in: $cell greater than 255");
916
            return($this->raiseError("Column in: $cell greater than 255"));
1002
        }
917
        }
1003
        // FIXME: change for BIFF8
918
        if ($row >= 16384) {
1004
        if ($row >= 16384) {
-
 
1005
            return $this->raiseError("Row in: $cell greater than 16384 ");
919
            return($this->raiseError("Row in: $cell greater than 16384 "));
1006
        }
920
        }
1007
 
-
 
1008
        // Set the high bits to indicate if row or col are relative.
-
 
1009
        if ($this->_BIFF_version == 0x0500) {
-
 
1010
            $row    |= $col_rel << 14;
-
 
1011
            $row    |= $row_rel << 15;
-
 
1012
            $col     = pack('C', $col);
921
    
1013
        } elseif ($this->_BIFF_version == 0x0600) {
922
        // Set the high bits to indicate if row or col are relative.
1014
            $col    |= $col_rel << 14;
923
        $row    |= $col_rel << 14;
-
 
924
        $row    |= $row_rel << 15;
1015
            $col    |= $row_rel << 15;
925
    
1016
            $col     = pack('v', $col);
926
        $row     = pack('v', $row);
1017
        }
927
        $col     = pack('C', $col);
1018
        $row     = pack('v', $row);
928
    
1019
 
929
        return(array($row, $col));
1020
        return array($row, $col);
930
    }
1021
    }
931
    
1022
 
932
    /**
1023
    /**
933
    * pack() row range into the required 3 byte format.
1024
    * pack() row range into the required 3 or 4 byte format.
934
    * Just using maximun col/rows, which is probably not the correct solution
1025
    * Just using maximum col/rows, which is probably not the correct solution
935
    *
1026
    *
Line 948... Line 1039...
948
        // Convert 1-index to zero-index
1039
        // Convert 1-index to zero-index
949
        $row1--;
1040
        $row1--;
950
        $row2--;
1041
        $row2--;
951
        // Trick poor inocent Excel
1042
        // Trick poor inocent Excel
952
        $col1 = 0;
1043
        $col1 = 0;
953
        $col2 = 16383; // maximum possible value for Excel 5 (change this!!!)
1044
        $col2 = 16383; // FIXME: maximum possible value for Excel 5 (change this!!!)
Line 954... Line 1045...
954
 
1045
 
955
        //list($row, $col, $row_rel, $col_rel) = $this->_cellToRowcol($cell);
1046
        // FIXME: this changes for BIFF8
956
        if (($row1 >= 16384) or ($row2 >= 16384)) {
1047
        if (($row1 >= 16384) or ($row2 >= 16384)) {
957
            return new PEAR_Error("Row in: $range greater than 16384 ");
1048
            return $this->raiseError("Row in: $range greater than 16384 ");
958
        }
1049
        }
959
    
1050
 
-
 
1051
        // Set the high bits to indicate if rows are relative.
-
 
1052
        if ($this->_BIFF_version == 0x0500) {
960
        // Set the high bits to indicate if rows are relative.
1053
            $row1    |= $row1_rel << 14; // FIXME: probably a bug
-
 
1054
            $row2    |= $row2_rel << 15;
-
 
1055
            $col1     = pack('C', $col1);
-
 
1056
            $col2     = pack('C', $col2);
-
 
1057
        } elseif ($this->_BIFF_version == 0x0600) {
961
        $row1    |= $row1_rel << 14;
1058
            $col1    |= $row1_rel << 15;
-
 
1059
            $col2    |= $row2_rel << 15;
-
 
1060
            $col1     = pack('v', $col1);
962
        $row2    |= $row2_rel << 15;
1061
            $col2     = pack('v', $col2);
963
    
1062
        }
964
        $row1     = pack('v', $row1);
1063
        $row1     = pack('v', $row1);
965
        $row2     = pack('v', $row2);
-
 
966
        $col1     = pack('C', $col1);
-
 
967
        $col2     = pack('C', $col2);
1064
        $row2     = pack('v', $row2);
968
    
1065
 
969
        return array($row1, $col1, $row2, $col2);
1066
        return array($row1, $col1, $row2, $col2);
Line 970... Line 1067...
970
    }
1067
    }
971
 
1068
 
Line 984... Line 1081...
984
        // return absolute column if there is a $ in the ref
1081
        // return absolute column if there is a $ in the ref
985
        $col_rel = empty($match[1]) ? 1 : 0;
1082
        $col_rel = empty($match[1]) ? 1 : 0;
986
        $col_ref = $match[2];
1083
        $col_ref = $match[2];
987
        $row_rel = empty($match[3]) ? 1 : 0;
1084
        $row_rel = empty($match[3]) ? 1 : 0;
988
        $row     = $match[4];
1085
        $row     = $match[4];
989
        
1086
 
990
        // Convert base26 column string to a number.
1087
        // Convert base26 column string to a number.
991
        $expn   = strlen($col_ref) - 1;
1088
        $expn   = strlen($col_ref) - 1;
992
        $col    = 0;
1089
        $col    = 0;
993
        for($i=0; $i < strlen($col_ref); $i++)
1090
        $col_ref_length = strlen($col_ref);
994
        {
1091
        for ($i = 0; $i < $col_ref_length; $i++) {
995
            $col += (ord($col_ref{$i}) - ord('A') + 1) * pow(26, $expn);
1092
            $col += (ord($col_ref{$i}) - ord('A') + 1) * pow(26, $expn);
996
            $expn--;
1093
            $expn--;
997
        }
1094
        }
998
    
1095
 
999
        // Convert 1-index to zero-index
1096
        // Convert 1-index to zero-index
1000
        $row--;
1097
        $row--;
1001
        $col--;
1098
        $col--;
1002
    
1099
 
1003
        return(array($row, $col, $row_rel, $col_rel));
1100
        return array($row, $col, $row_rel, $col_rel);
1004
    }
1101
    }
1005
    
1102
 
1006
    /**
1103
    /**
1007
    * Advance to the next valid token.
1104
    * Advance to the next valid token.
1008
    *
1105
    *
1009
    * @access private
1106
    * @access private
1010
    */
1107
    */
1011
    function _advance()
1108
    function _advance()
1012
    {
1109
    {
1013
        $i = $this->_current_char;
1110
        $i = $this->_current_char;
-
 
1111
        $formula_length = strlen($this->_formula);
1014
        // eat up white spaces
1112
        // eat up white spaces
1015
        if($i < strlen($this->_formula))
1113
        if ($i < $formula_length) {
1016
        {
-
 
1017
            while($this->_formula{$i} == " ") {
1114
            while ($this->_formula{$i} == " ") {
1018
                $i++;
1115
                $i++;
1019
            }
1116
            }
-
 
1117
 
1020
            if($i < strlen($this->_formula) - 1) {
1118
            if ($i < ($formula_length - 1)) {
1021
                $this->_lookahead = $this->_formula{$i+1};
1119
                $this->_lookahead = $this->_formula{$i+1};
1022
            }
1120
            }
1023
            $token = "";
1121
            $token = '';
1024
        }
1122
        }
-
 
1123
 
1025
        while($i < strlen($this->_formula))
1124
        while ($i < $formula_length) {
1026
        {
-
 
1027
            $token .= $this->_formula{$i};
1125
            $token .= $this->_formula{$i};
-
 
1126
            if ($i < ($formula_length - 1)) {
-
 
1127
                $this->_lookahead = $this->_formula{$i+1};
-
 
1128
            } else {
1028
            if($this->_match($token) != '')
1129
                $this->_lookahead = '';
1029
            {
1130
            }
-
 
1131
 
-
 
1132
            if ($this->_match($token) != '') {
1030
                if($i < strlen($this->_formula) - 1) {
1133
                //if ($i < strlen($this->_formula) - 1) {
1031
                    $this->_lookahead = $this->_formula{$i+1};
1134
                //    $this->_lookahead = $this->_formula{$i+1};
1032
                }
1135
                //}
1033
                $this->_current_char = $i + 1;
1136
                $this->_current_char = $i + 1;
1034
                $this->_current_token = $token;
1137
                $this->_current_token = $token;
1035
                return(1);
1138
                return 1;
1036
            }
1139
            }
-
 
1140
 
1037
            if ($i < strlen($this->_formula) - 2) {
1141
            if ($i < ($formula_length - 2)) {
1038
                $this->_lookahead = $this->_formula{$i+2};
1142
                $this->_lookahead = $this->_formula{$i+2};
1039
            }
-
 
1040
            // if we run out of characters _lookahead becomes empty
1143
            } else { // if we run out of characters _lookahead becomes empty
1041
            else {
-
 
1042
                $this->_lookahead = '';
1144
                $this->_lookahead = '';
1043
            }
1145
            }
1044
            $i++;
1146
            $i++;
1045
        }
1147
        }
1046
        //die("Lexical error ".$this->_current_char);
1148
        //die("Lexical error ".$this->_current_char);
1047
    }
1149
    }
1048
    
1150
 
1049
    /**
1151
    /**
1050
    * Checks if it's a valid token.
1152
    * Checks if it's a valid token.
1051
    *
1153
    *
1052
    * @access private
1154
    * @access private
1053
    * @param mixed $token The token to check.
1155
    * @param mixed $token The token to check.
1054
    * @return mixed       The checked token or false on failure
1156
    * @return mixed       The checked token or false on failure
1055
    */
1157
    */
1056
    function _match($token)
1158
    function _match($token)
1057
    {
1159
    {
1058
        switch($token)
1160
        switch($token) {
1059
        {
-
 
1060
            case SPREADSHEET_EXCEL_WRITER_ADD:
1161
            case SPREADSHEET_EXCEL_WRITER_ADD:
1061
                return($token);
1162
                return $token;
1062
                break;
1163
                break;
1063
            case SPREADSHEET_EXCEL_WRITER_SUB:
1164
            case SPREADSHEET_EXCEL_WRITER_SUB:
1064
                return($token);
1165
                return $token;
1065
                break;
1166
                break;
1066
            case SPREADSHEET_EXCEL_WRITER_MUL:
1167
            case SPREADSHEET_EXCEL_WRITER_MUL:
1067
                return($token);
1168
                return $token;
1068
                break;
1169
                break;
1069
            case SPREADSHEET_EXCEL_WRITER_DIV:
1170
            case SPREADSHEET_EXCEL_WRITER_DIV:
1070
                return($token);
1171
                return $token;
1071
                break;
1172
                break;
1072
            case SPREADSHEET_EXCEL_WRITER_OPEN:
1173
            case SPREADSHEET_EXCEL_WRITER_OPEN:
1073
                return($token);
1174
                return $token;
1074
                break;
1175
                break;
1075
            case SPREADSHEET_EXCEL_WRITER_CLOSE:
1176
            case SPREADSHEET_EXCEL_WRITER_CLOSE:
1076
                return($token);
1177
                return $token;
1077
                break;
1178
                break;
1078
            case SPREADSHEET_EXCEL_WRITER_COMA:
1179
            case SPREADSHEET_EXCEL_WRITER_COMA:
1079
                return($token);
1180
                return $token;
-
 
1181
                break;
-
 
1182
            case SPREADSHEET_EXCEL_WRITER_SEMICOLON:
-
 
1183
                return $token;
1080
                break;
1184
                break;
1081
            case SPREADSHEET_EXCEL_WRITER_GT:
1185
            case SPREADSHEET_EXCEL_WRITER_GT:
1082
                if ($this->_lookahead == '=') { // it's a GE token
1186
                if ($this->_lookahead == '=') { // it's a GE token
1083
                    break;
1187
                    break;
1084
                }
1188
                }
1085
                return($token);
1189
                return $token;
1086
                break;
1190
                break;
1087
            case SPREADSHEET_EXCEL_WRITER_LT:
1191
            case SPREADSHEET_EXCEL_WRITER_LT:
1088
                // it's a LE or a NE token
1192
                // it's a LE or a NE token
1089
                if (($this->_lookahead == '=') or ($this->_lookahead == '>')) {
1193
                if (($this->_lookahead == '=') or ($this->_lookahead == '>')) {
1090
                    break;
1194
                    break;
1091
                }
1195
                }
1092
                return($token);
1196
                return $token;
1093
                break;
1197
                break;
1094
            case SPREADSHEET_EXCEL_WRITER_GE:
1198
            case SPREADSHEET_EXCEL_WRITER_GE:
1095
                return($token);
1199
                return $token;
1096
                break;
1200
                break;
1097
            case SPREADSHEET_EXCEL_WRITER_LE:
1201
            case SPREADSHEET_EXCEL_WRITER_LE:
1098
                return($token);
1202
                return $token;
1099
                break;
1203
                break;
1100
            case SPREADSHEET_EXCEL_WRITER_EQ:
1204
            case SPREADSHEET_EXCEL_WRITER_EQ:
1101
                return($token);
1205
                return $token;
1102
                break;
1206
                break;
1103
            case SPREADSHEET_EXCEL_WRITER_NE:
1207
            case SPREADSHEET_EXCEL_WRITER_NE:
1104
                return($token);
1208
                return $token;
-
 
1209
                break;
-
 
1210
            case SPREADSHEET_EXCEL_WRITER_CONCAT:
-
 
1211
                return $token;
1105
                break;
1212
                break;
1106
            default:
1213
            default:
1107
                // if it's a reference
1214
                // if it's a reference
1108
                if (preg_match('/^\$?[A-I]?[A-Z]\$?[0-9]+$/',$token) and
1215
                if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$token) and
1109
                   !ereg("[0-9]",$this->_lookahead) and 
1216
                   !preg_match("/[0-9]/",$this->_lookahead) and 
1110
                   ($this->_lookahead != ':') and ($this->_lookahead != '.') and
1217
                   ($this->_lookahead != ':') and ($this->_lookahead != '.') and
1111
                   ($this->_lookahead != '!'))
1218
                   ($this->_lookahead != '!'))
1112
                {
1219
                {
1113
                    return $token;
1220
                    return $token;
1114
                }
1221
                }
1115
                // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
1222
                // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
-
 
1223
                elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/u",$token) and
-
 
1224
                       !preg_match("/[0-9]/",$this->_lookahead) and
-
 
1225
                       ($this->_lookahead != ':') and ($this->_lookahead != '.'))
-
 
1226
                {
-
 
1227
                    return $token;
-
 
1228
                }
-
 
1229
                // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1)
1116
                elseif (preg_match("/^[A-Za-z0-9_]+(\:[A-Za-z0-9_]+)?\![A-I]?[A-Z][0-9]+$/",$token) and
1230
                elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/u",$token) and
1117
                       !ereg("[0-9]",$this->_lookahead) and
1231
                       !preg_match("/[0-9]/",$this->_lookahead) and
1118
                       ($this->_lookahead != ':') and ($this->_lookahead != '.'))
1232
                       ($this->_lookahead != ':') and ($this->_lookahead != '.'))
1119
                {
1233
                {
1120
                    return $token;
1234
                    return $token;
1121
                }
1235
                }
1122
                // if it's a range (A1:A2)
1236
                // if it's a range (A1:A2)
1123
                elseif (preg_match("/^(\$)?[A-I]?[A-Z](\$)?[0-9]+:(\$)?[A-I]?[A-Z](\$)?[0-9]+$/",$token) and 
1237
                elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and 
1124
                       !ereg("[0-9]",$this->_lookahead))
1238
                       !preg_match("/[0-9]/",$this->_lookahead))
1125
                {
1239
                {
1126
                    return $token;
1240
                    return $token;
1127
                }
1241
                }
1128
                // if it's a range (A1..A2)
1242
                // if it's a range (A1..A2)
1129
                elseif (preg_match("/^(\$)?[A-I]?[A-Z](\$)?[0-9]+\.\.(\$)?[A-I]?[A-Z](\$)?[0-9]+$/",$token) and 
1243
                elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and 
1130
                       !ereg("[0-9]",$this->_lookahead))
1244
                       !preg_match("/[0-9]/",$this->_lookahead))
1131
                {
1245
                {
1132
                    return $token;
1246
                    return $token;
1133
                }
1247
                }
-
 
1248
                // If it's an external range like Sheet1!A1 or Sheet1:Sheet2!A1:B2
-
 
1249
                elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$token) and
-
 
1250
                       !preg_match("/[0-9]/",$this->_lookahead))
-
 
1251
                {
1134
                // If it's an external range
1252
                    return $token;
-
 
1253
                }
-
 
1254
                // If it's an external range like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
1135
                elseif (preg_match("/^[A-Za-z0-9_]+(\:[A-Za-z0-9_]+)?\!([A-I]?[A-Z])?[0-9]+:([A-I]?[A-Z])?[0-9]+$/",$token) and
1255
                elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$token) and
1136
                       !ereg("[0-9]",$this->_lookahead))
1256
                       !preg_match("/[0-9]/",$this->_lookahead))
1137
                {
1257
                {
1138
                    return $token;
1258
                    return $token;
1139
                }
1259
                }
1140
                // If it's a number (check that it's not a sheet name or range)
1260
                // If it's a number (check that it's not a sheet name or range)
-
 
1261
                elseif (is_numeric($token) and 
1141
                elseif (is_numeric($token) and !is_numeric($token.$this->_lookahead) and
1262
                        (!is_numeric($token.$this->_lookahead) or ($this->_lookahead == '')) and
1142
                        ($this->_lookahead != '!') and (($this->_lookahead != ':')))
1263
                        ($this->_lookahead != '!') and ($this->_lookahead != ':'))
1143
                {
1264
                {
1144
                    return $token;
1265
                    return $token;
1145
                }
1266
                }
1146
                // If it's a string (of maximum 255 characters)
1267
                // If it's a string (of maximum 255 characters)
1147
                elseif(ereg("^\"[^\"]{0,255}\"$",$token))
1268
                elseif (preg_match("/^\"[^\"]{0,255}\"$/",$token))
1148
                {
1269
                {
1149
                    return($token);
1270
                    return $token;
1150
                }
1271
                }
1151
                // if it's a function call
1272
                // if it's a function call
1152
                elseif(eregi("^[A-Z0-9\xc0-\xdc\.]+$",$token) and ($this->_lookahead == "("))
1273
                elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i",$token) and ($this->_lookahead == "("))
1153
                {
1274
                {
1154
                    return($token);
1275
                    return $token;
1155
                }
1276
                }
1156
                return '';
1277
                return '';
1157
        }
1278
        }
1158
    }
1279
    }
1159
    
1280
 
1160
    /**
1281
    /**
1161
    * The parsing method. It parses a formula.
1282
    * The parsing method. It parses a formula.
1162
    *
1283
    *
1163
    * @access public
1284
    * @access public
1164
    * @param string $formula The formula to parse, without the initial equal sign (=).
1285
    * @param string $formula The formula to parse, without the initial equal
-
 
1286
    *                        sign (=).
-
 
1287
    * @return mixed true on success, PEAR_Error on failure
1165
    */
1288
    */
1166
    function parse($formula)
1289
    function parse($formula)
1167
    {
1290
    {
1168
        $this->_current_char = 0;
1291
        $this->_current_char = 0;
1169
        $this->_formula      = $formula;
1292
        $this->_formula      = $formula;
1170
        $this->_lookahead    = $formula{1};
1293
        $this->_lookahead    = $formula{1};
1171
        $this->_advance();
1294
        $this->_advance();
1172
        $this->_parse_tree   = $this->_condition();
1295
        $this->_parse_tree   = $this->_condition();
1173
        if ($this->isError($this->_parse_tree)) {
1296
        if (PEAR::isError($this->_parse_tree)) {
1174
            return $this->_parse_tree;
1297
            return $this->_parse_tree;
1175
        }
1298
        }
-
 
1299
        return true;
1176
    }
1300
    }
1177
    
1301
 
1178
    /**
1302
    /**
1179
    * It parses a condition. It assumes the following rule:
1303
    * It parses a condition. It assumes the following rule:
1180
    * Cond -> Expr [(">" | "<") Expr]
1304
    * Cond -> Expr [(">" | "<") Expr]
1181
    *
1305
    *
1182
    * @access private
1306
    * @access private
1183
    * @return mixed The parsed ptg'd tree
1307
    * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
1184
    */
1308
    */
1185
    function _condition()
1309
    function _condition()
1186
    {
1310
    {
1187
        $result = $this->_expression();
1311
        $result = $this->_expression();
1188
        if($this->isError($result)) {
1312
        if (PEAR::isError($result)) {
1189
            return $result;
1313
            return $result;
1190
        }
1314
        }
1191
        if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LT)
1315
        if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LT) {
1192
        {
-
 
1193
            $this->_advance();
1316
            $this->_advance();
1194
            $result2 = $this->_expression();
1317
            $result2 = $this->_expression();
1195
            if($this->isError($result2)) {
1318
            if (PEAR::isError($result2)) {
1196
                return $result2;
1319
                return $result2;
1197
            }
1320
            }
1198
            $result = $this->_createTree('ptgLT', $result, $result2);
1321
            $result = $this->_createTree('ptgLT', $result, $result2);
1199
        }
-
 
1200
        elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GT) 
1322
        } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GT) {
1201
        {
-
 
1202
            $this->_advance();
1323
            $this->_advance();
1203
            $result2 = $this->_expression();
1324
            $result2 = $this->_expression();
1204
            if($this->isError($result2)) {
1325
            if (PEAR::isError($result2)) {
1205
                return $result2;
1326
                return $result2;
1206
            }
1327
            }
1207
            $result = $this->_createTree('ptgGT', $result, $result2);
1328
            $result = $this->_createTree('ptgGT', $result, $result2);
1208
        }
-
 
1209
        elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LE) 
1329
        } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LE) {
1210
        {
-
 
1211
            $this->_advance();
1330
            $this->_advance();
1212
            $result2 = $this->_expression();
1331
            $result2 = $this->_expression();
1213
            if($this->isError($result2)) {
1332
            if (PEAR::isError($result2)) {
1214
                return $result2;
1333
                return $result2;
1215
            }
1334
            }
1216
            $result = $this->_createTree('ptgLE', $result, $result2);
1335
            $result = $this->_createTree('ptgLE', $result, $result2);
1217
        }
-
 
1218
        elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GE) 
1336
        } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GE) {
1219
        {
-
 
1220
            $this->_advance();
1337
            $this->_advance();
1221
            $result2 = $this->_expression();
1338
            $result2 = $this->_expression();
1222
            if($this->isError($result2)) {
1339
            if (PEAR::isError($result2)) {
1223
                return $result2;
1340
                return $result2;
1224
            }
1341
            }
1225
            $result = $this->_createTree('ptgGE', $result, $result2);
1342
            $result = $this->_createTree('ptgGE', $result, $result2);
1226
        }
-
 
1227
        elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_EQ) 
1343
        } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_EQ) {
1228
        {
-
 
1229
            $this->_advance();
1344
            $this->_advance();
1230
            $result2 = $this->_expression();
1345
            $result2 = $this->_expression();
1231
            if($this->isError($result2)) {
1346
            if (PEAR::isError($result2)) {
1232
                return $result2;
1347
                return $result2;
1233
            }
1348
            }
1234
            $result = $this->_createTree('ptgEQ', $result, $result2);
1349
            $result = $this->_createTree('ptgEQ', $result, $result2);
1235
        }
-
 
1236
        elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_NE) 
1350
        } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_NE) {
1237
        {
-
 
1238
            $this->_advance();
1351
            $this->_advance();
1239
            $result2 = $this->_expression();
1352
            $result2 = $this->_expression();
1240
            if($this->isError($result2)) {
1353
            if (PEAR::isError($result2)) {
1241
                return $result2;
1354
                return $result2;
1242
            }
1355
            }
1243
            $result = $this->_createTree('ptgNE', $result, $result2);
1356
            $result = $this->_createTree('ptgNE', $result, $result2);
-
 
1357
        } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_CONCAT) {
-
 
1358
            $this->_advance();
-
 
1359
            $result2 = $this->_expression();
-
 
1360
            if (PEAR::isError($result2)) {
-
 
1361
                return $result2;
-
 
1362
        }
-
 
1363
            $result = $this->_createTree('ptgConcat', $result, $result2);
1244
        }
1364
        }
1245
        return $result;
1365
        return $result;
1246
    }
1366
    }
-
 
1367
 
1247
    /**
1368
    /**
1248
    * It parses a expression. It assumes the following rule:
1369
    * It parses a expression. It assumes the following rule:
1249
    * Expr -> Term [("+" | "-") Term]
1370
    * Expr -> Term [("+" | "-") Term]
-
 
1371
    *      -> "string"
-
 
1372
    *      -> "-" Term
1250
    *
1373
    *
1251
    * @access private
1374
    * @access private
1252
    * @return mixed The parsed ptg'd tree
1375
    * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
1253
    */
1376
    */
1254
    function _expression()
1377
    function _expression()
1255
    {
1378
    {
1256
        // If it's a string return a string node
1379
        // If it's a string return a string node
1257
        if (ereg("^\"[^\"]{0,255}\"$", $this->_current_token))
1380
        if (preg_match("/^\"[^\"]{0,255}\"$/", $this->_current_token)) {
1258
        {
-
 
1259
            $result = $this->_createTree($this->_current_token, '', '');
1381
            $result = $this->_createTree($this->_current_token, '', '');
1260
            $this->_advance();
1382
            $this->_advance();
1261
            return($result);
1383
            return $result;
-
 
1384
        } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB) {
-
 
1385
            // catch "-" Term
-
 
1386
            $this->_advance();
-
 
1387
            $result2 = $this->_expression();
-
 
1388
            $result = $this->_createTree('ptgUminus', $result2, '');
-
 
1389
            return $result;
1262
        }
1390
        }
1263
        $result = $this->_term();
1391
        $result = $this->_term();
1264
        if($this->isError($result)) {
1392
        if (PEAR::isError($result)) {
1265
            return($result);
1393
            return $result;
1266
        }
1394
        }
1267
        while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD) or 
1395
        while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD) or
1268
               ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB))
1396
               ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB)) {
1269
        {
1397
        /**/
1270
            if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD)
1398
            if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD) {
1271
            {
-
 
1272
                $this->_advance();
1399
                $this->_advance();
1273
                $result2 = $this->_term();
1400
                $result2 = $this->_term();
1274
                if($this->isError($result2)) {
1401
                if (PEAR::isError($result2)) {
1275
                    return($result2);
1402
                    return $result2;
1276
                }
1403
                }
1277
                $result = $this->_createTree('ptgAdd', $result, $result2);
1404
                $result = $this->_createTree('ptgAdd', $result, $result2);
1278
            }
-
 
1279
            else 
1405
            } else {
1280
            {
-
 
1281
                $this->_advance();
1406
                $this->_advance();
1282
                $result2 = $this->_term();
1407
                $result2 = $this->_term();
1283
                if($this->isError($result2)) {
1408
                if (PEAR::isError($result2)) {
1284
                    return($result2);
1409
                    return $result2;
1285
                }
1410
                }
1286
                $result = $this->_createTree('ptgSub', $result, $result2);
1411
                $result = $this->_createTree('ptgSub', $result, $result2);
1287
            }
1412
            }
1288
        }
1413
        }
1289
        return($result);
1414
        return $result;
1290
    }
1415
    }
1291
    
1416
 
1292
    /**
1417
    /**
1293
    * This function just introduces a ptgParen element in the tree, so that Excel
1418
    * This function just introduces a ptgParen element in the tree, so that Excel
1294
    * doesn't get confused when working with a parenthesized formula afterwards.
1419
    * doesn't get confused when working with a parenthesized formula afterwards.
1295
    *
1420
    *
1296
    * @access private
1421
    * @access private
1297
    * @see _fact()
1422
    * @see _fact()
1298
    * @return mixed The parsed ptg'd tree
1423
    * @return array The parsed ptg'd tree
1299
    */
1424
    */
1300
    function _parenthesizedExpression()
1425
    function _parenthesizedExpression()
1301
    {
1426
    {
1302
        $result = $this->_createTree('ptgParen', $this->_expression(), '');
1427
        $result = $this->_createTree('ptgParen', $this->_expression(), '');
1303
        return($result);
1428
        return $result;
1304
    }
1429
    }
1305
    
1430
 
1306
    /**
1431
    /**
1307
    * It parses a term. It assumes the following rule:
1432
    * It parses a term. It assumes the following rule:
1308
    * Term -> Fact [("*" | "/") Fact]
1433
    * Term -> Fact [("*" | "/") Fact]
1309
    *
1434
    *
1310
    * @access private
1435
    * @access private
1311
    * @return mixed The parsed ptg'd tree
1436
    * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
1312
    */
1437
    */
1313
    function _term()
1438
    function _term()
1314
    {
1439
    {
1315
        $result = $this->_fact();
1440
        $result = $this->_fact();
1316
        if($this->isError($result)) {
1441
        if (PEAR::isError($result)) {
1317
            return($result);
1442
            return $result;
1318
        }
1443
        }
1319
        while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL) or 
1444
        while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL) or
1320
               ($this->_current_token == SPREADSHEET_EXCEL_WRITER_DIV))
1445
               ($this->_current_token == SPREADSHEET_EXCEL_WRITER_DIV)) {
1321
        {
1446
        /**/
1322
            if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL)
1447
            if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL) {
1323
            {
-
 
1324
                $this->_advance();
1448
                $this->_advance();
1325
                $result2 = $this->_fact();
1449
                $result2 = $this->_fact();
1326
                if($this->isError($result2)) {
1450
                if (PEAR::isError($result2)) {
1327
                    return($result2);
1451
                    return $result2;
1328
                }
1452
                }
1329
                $result = $this->_createTree('ptgMul', $result, $result2);
1453
                $result = $this->_createTree('ptgMul', $result, $result2);
1330
            }
-
 
1331
            else 
1454
            } else {
1332
            {
-
 
1333
                $this->_advance();
1455
                $this->_advance();
1334
                $result2 = $this->_fact();
1456
                $result2 = $this->_fact();
1335
                if($this->isError($result2)) {
1457
                if (PEAR::isError($result2)) {
1336
                    return($result2);
1458
                    return $result2;
1337
                }
1459
                }
1338
                $result = $this->_createTree('ptgDiv', $result, $result2);
1460
                $result = $this->_createTree('ptgDiv', $result, $result2);
1339
            }
1461
            }
1340
        }
1462
        }
1341
        return($result);
1463
        return $result;
1342
    }
1464
    }
1343
    
1465
 
1344
    /**
1466
    /**
1345
    * It parses a factor. It assumes the following rule:
1467
    * It parses a factor. It assumes the following rule:
1346
    * Fact -> ( Expr )
1468
    * Fact -> ( Expr )
1347
    *       | CellRef
1469
    *       | CellRef
1348
    *       | CellRange
1470
    *       | CellRange
1349
    *       | Number
1471
    *       | Number
1350
    *       | Function
1472
    *       | Function
1351
    *
1473
    *
1352
    * @access private
1474
    * @access private
1353
    * @return mixed The parsed ptg'd tree
1475
    * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
1354
    */
1476
    */
1355
    function _fact()
1477
    function _fact()
1356
    {
1478
    {
1357
        if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_OPEN)
1479
        if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_OPEN) {
1358
        {
-
 
1359
            $this->_advance();         // eat the "("
1480
            $this->_advance();         // eat the "("
1360
            $result = $this->_parenthesizedExpression();
1481
            $result = $this->_parenthesizedExpression();
1361
            if ($this->_current_token != SPREADSHEET_EXCEL_WRITER_CLOSE) {
1482
            if ($this->_current_token != SPREADSHEET_EXCEL_WRITER_CLOSE) {
1362
                return($this->raiseError("')' token expected."));
1483
                return $this->raiseError("')' token expected.");
1363
            }
1484
            }
1364
            $this->_advance();         // eat the ")"
1485
            $this->_advance();         // eat the ")"
1365
            return $result;
1486
            return $result;
1366
        }
1487
        }
1367
        // if it's a reference
1488
        // if it's a reference
1368
        if (preg_match('/^\$?[A-I]?[A-Z]\$?[0-9]+$/',$this->_current_token))
1489
        if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$this->_current_token))
1369
        {
1490
        {
1370
            $result = $this->_createTree($this->_current_token, '', '');
1491
            $result = $this->_createTree($this->_current_token, '', '');
1371
            $this->_advance();
1492
            $this->_advance();
1372
            return $result;
1493
            return $result;
1373
        }
1494
        }
1374
        // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
1495
        // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
-
 
1496
        elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/u",$this->_current_token))
-
 
1497
        {
-
 
1498
            $result = $this->_createTree($this->_current_token, '', '');
-
 
1499
            $this->_advance();
-
 
1500
            return $result;
-
 
1501
        }
-
 
1502
        // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1)
1375
        elseif (preg_match("/^[A-Za-z0-9_]+(\:[A-Za-z0-9_]+)?\![A-I]?[A-Z][0-9]+$/",$this->_current_token))
1503
        elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/u",$this->_current_token))
1376
        {
1504
        {
1377
            $result = $this->_createTree($this->_current_token, '', '');
1505
            $result = $this->_createTree($this->_current_token, '', '');
1378
            $this->_advance();
1506
            $this->_advance();
1379
            return $result;
1507
            return $result;
1380
        }
1508
        }
1381
        // if it's a range
1509
        // if it's a range
1382
        elseif (preg_match("/^(\$)?[A-I]?[A-Z](\$)?[0-9]+:(\$)?[A-I]?[A-Z](\$)?[0-9]+$/",$this->_current_token) or 
1510
        elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token) or 
1383
                preg_match("/^(\$)?[A-I]?[A-Z](\$)?[0-9]+\.\.(\$)?[A-I]?[A-Z](\$)?[0-9]+$/",$this->_current_token))
1511
                preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token))
-
 
1512
        {
-
 
1513
            $result = $this->_current_token;
-
 
1514
            $this->_advance();
-
 
1515
            return $result;
-
 
1516
        }
-
 
1517
        // If it's an external range (Sheet1!A1 or Sheet1!A1:B2)
-
 
1518
        elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$this->_current_token))
1384
        {
1519
        {
1385
            $result = $this->_current_token;
1520
            $result = $this->_current_token;
1386
            $this->_advance();
1521
            $this->_advance();
1387
            return $result;
1522
            return $result;
1388
        }
1523
        }
1389
        // If it's an external range (Sheet1!A1:B2)
1524
        // If it's an external range ('Sheet1'!A1 or 'Sheet1'!A1:B2)
1390
        elseif (preg_match("/^[A-Za-z0-9_]+(\:[A-Za-z0-9_]+)?\!([A-I]?[A-Z])?[0-9]+:([A-I]?[A-Z])?[0-9]+$/",$this->_current_token))
1525
        elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$this->_current_token))
1391
        {
1526
        {
1392
            $result = $this->_current_token;
1527
            $result = $this->_current_token;
1393
            $this->_advance();
1528
            $this->_advance();
1394
            return($result);
1529
            return $result;
1395
        }
1530
        }
1396
        elseif (is_numeric($this->_current_token))
1531
        elseif (is_numeric($this->_current_token))
1397
        {
1532
        {
1398
            $result = $this->_createTree($this->_current_token, '', '');
1533
            $result = $this->_createTree($this->_current_token, '', '');
1399
            $this->_advance();
1534
            $this->_advance();
1400
            return($result);
1535
            return $result;
1401
        }
1536
        }
1402
        // if it's a function call
1537
        // if it's a function call
1403
        elseif (eregi("^[A-Z0-9\xc0-\xdc\.]+$",$this->_current_token))
1538
        elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i",$this->_current_token))
1404
        {
1539
        {
1405
            $result = $this->_func();
1540
            $result = $this->_func();
1406
            return($result);
1541
            return $result;
1407
        }
1542
        }
1408
        return($this->raiseError("Sintactic error: ".$this->_current_token.", lookahead: ".
1543
        return $this->raiseError("Syntax error: ".$this->_current_token.
-
 
1544
                                 ", lookahead: ".$this->_lookahead.
1409
                                 $this->_lookahead.", current char: ".$this->_current_char));
1545
                                 ", current char: ".$this->_current_char);
1410
    }
1546
    }
1411
    
1547
 
1412
    /**
1548
    /**
1413
    * It parses a function call. It assumes the following rule:
1549
    * It parses a function call. It assumes the following rule:
1414
    * Func -> ( Expr [,Expr]* )
1550
    * Func -> ( Expr [,Expr]* )
1415
    *
1551
    *
1416
    * @access private
1552
    * @access private
-
 
1553
    * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
1417
    */
1554
    */
1418
    function _func()
1555
    function _func()
1419
    {
1556
    {
1420
        $num_args = 0; // number of arguments received
1557
        $num_args = 0; // number of arguments received
1421
        $function = $this->_current_token;
1558
        $function = strtoupper($this->_current_token);
-
 
1559
        $result   = ''; // initialize result
1422
        $this->_advance();
1560
        $this->_advance();
1423
        $this->_advance();         // eat the "("
1561
        $this->_advance();         // eat the "("
1424
        while($this->_current_token != ')')
1562
        while ($this->_current_token != ')') {
1425
        {
1563
        /**/
1426
            if($num_args > 0)
1564
            if ($num_args > 0) {
1427
            {
-
 
1428
                if($this->_current_token == SPREADSHEET_EXCEL_WRITER_COMA) {
1565
                if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_COMA or
1429
                    $this->_advance();  // eat the ","
1566
                    $this->_current_token == SPREADSHEET_EXCEL_WRITER_SEMICOLON)
1430
                }
1567
                {
-
 
1568
                    $this->_advance();  // eat the "," or ";"
1431
                else {
1569
                } else {
1432
                    return new PEAR_Error("Sintactic error: coma expected in ".
1570
                    return $this->raiseError("Syntax error: comma expected in ".
1433
                                          "function $function, {$num_args}� arg");
1571
                                      "function $function, arg #{$num_args}");
1434
                }
1572
                }
1435
                $result2 = $this->_condition();
1573
                $result2 = $this->_condition();
1436
                if($this->isError($result2)) {
1574
                if (PEAR::isError($result2)) {
1437
                    return($result2);
1575
                    return $result2;
1438
                }
1576
                }
1439
                $result = $this->_createTree('arg', $result, $result2);
1577
                $result = $this->_createTree('arg', $result, $result2);
1440
            }
-
 
1441
            else // first argument
1578
            } else { // first argument
1442
            {
-
 
1443
                $result2 = $this->_condition();
1579
                $result2 = $this->_condition();
1444
                if($this->isError($result2)) {
1580
                if (PEAR::isError($result2)) {
1445
                    return($result2);
1581
                    return $result2;
1446
                }
1582
                }
1447
                $result = $this->_createTree('arg', '', $result2);
1583
                $result = $this->_createTree('arg', '', $result2);
1448
            }
1584
            }
1449
            $num_args++;
1585
            $num_args++;
1450
        }
1586
        }
-
 
1587
        if (!isset($this->_functions[$function])) {
-
 
1588
            return $this->raiseError("Function $function() doesn't exist");
-
 
1589
        }
1451
        $args = $this->_functions[$function][1];
1590
        $args = $this->_functions[$function][1];
1452
        // If fixed number of args eg. TIME($i,$j,$k). Check that the number of args is valid.
1591
        // If fixed number of args eg. TIME($i,$j,$k). Check that the number of args is valid.
1453
        if (($args >= 0) and ($args != $num_args))
1592
        if (($args >= 0) and ($args != $num_args)) {
1454
        {
-
 
1455
            return($this->raiseError("Incorrect number of arguments in function $function() "));
1593
            return $this->raiseError("Incorrect number of arguments in function $function() ");
1456
        }
1594
        }
1457
    
1595
 
1458
        $result = $this->_createTree($function, $result, '');
1596
        $result = $this->_createTree($function, $result, $num_args);
1459
        $this->_advance();         // eat the ")"
1597
        $this->_advance();         // eat the ")"
1460
        return($result);
1598
        return $result;
1461
    }
1599
    }
1462
    
1600
 
1463
    /**
1601
    /**
1464
    * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
1602
    * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
1465
    * as elements.
1603
    * as elements.
1466
    *
1604
    *
1467
    * @access private
1605
    * @access private
1468
    * @param mixed $value The value of this node.
1606
    * @param mixed $value The value of this node.
1469
    * @param mixed $left  The left array (sub-tree) or a final node.
1607
    * @param mixed $left  The left array (sub-tree) or a final node.
1470
    * @param mixed $right The right array (sub-tree) or a final node.
1608
    * @param mixed $right The right array (sub-tree) or a final node.
-
 
1609
    * @return array A tree
1471
    */
1610
    */
1472
    function _createTree($value, $left, $right)
1611
    function _createTree($value, $left, $right)
1473
    {
1612
    {
1474
        return(array('value' => $value, 'left' => $left, 'right' => $right));
1613
        return array('value' => $value, 'left' => $left, 'right' => $right);
1475
    }
1614
    }
1476
    
1615
 
1477
    /**
1616
    /**
1478
    * Builds a string containing the tree in reverse polish notation (What you 
1617
    * Builds a string containing the tree in reverse polish notation (What you
1479
    * would use in a HP calculator stack).
1618
    * would use in a HP calculator stack).
1480
    * The following tree:
1619
    * The following tree:
1481
    * 
1620
    *
1482
    *    +
1621
    *    +
1483
    *   / \
1622
    *   / \
1484
    *  2   3
1623
    *  2   3
1485
    *
1624
    *
1486
    * produces: "23+"
1625
    * produces: "23+"
Line 1502... Line 1641...
1502
    * @return string The tree in reverse polish notation
1641
    * @return string The tree in reverse polish notation
1503
    */
1642
    */
1504
    function toReversePolish($tree = array())
1643
    function toReversePolish($tree = array())
1505
    {
1644
    {
1506
        $polish = ""; // the string we are going to return
1645
        $polish = ""; // the string we are going to return
1507
        if (empty($tree)) // If it's the first call use _parse_tree
1646
        if (empty($tree)) { // If it's the first call use _parse_tree
1508
        {
-
 
1509
            $tree = $this->_parse_tree;
1647
            $tree = $this->_parse_tree;
1510
        }
1648
        }
1511
        if (is_array($tree['left']))
1649
        if (is_array($tree['left'])) {
1512
        {
-
 
1513
            $converted_tree = $this->toReversePolish($tree['left']);
1650
            $converted_tree = $this->toReversePolish($tree['left']);
1514
            if($this->isError($converted_tree)) {
1651
            if (PEAR::isError($converted_tree)) {
1515
                return($converted_tree);
1652
                return $converted_tree;
1516
                }
1653
            }
1517
            $polish .= $converted_tree;
1654
            $polish .= $converted_tree;
1518
        }
-
 
1519
        elseif($tree['left'] != '') // It's a final node
1655
        } elseif ($tree['left'] != '') { // It's a final node
1520
        {
-
 
1521
            $converted_tree = $this->_convert($tree['left']);
1656
            $converted_tree = $this->_convert($tree['left']);
1522
            if($this->isError($converted_tree)) {
1657
            if (PEAR::isError($converted_tree)) {
1523
                return($converted_tree);
1658
                return $converted_tree;
1524
                }
1659
            }
1525
            $polish .= $converted_tree;
1660
            $polish .= $converted_tree;
1526
        }
1661
        }
1527
        if (is_array($tree['right']))
1662
        if (is_array($tree['right'])) {
1528
        {
-
 
1529
            $converted_tree = $this->toReversePolish($tree['right']);
1663
            $converted_tree = $this->toReversePolish($tree['right']);
1530
            if($this->isError($converted_tree)) {
1664
            if (PEAR::isError($converted_tree)) {
1531
                return($converted_tree);
1665
                return $converted_tree;
1532
                }
1666
            }
1533
            $polish .= $converted_tree;
1667
            $polish .= $converted_tree;
1534
        }
-
 
1535
        elseif($tree['right'] != '') // It's a final node
1668
        } elseif ($tree['right'] != '') { // It's a final node
1536
        {
-
 
1537
            $converted_tree = $this->_convert($tree['right']);
1669
            $converted_tree = $this->_convert($tree['right']);
1538
            if($this->isError($converted_tree)) {
1670
            if (PEAR::isError($converted_tree)) {
1539
                return($converted_tree);
1671
                return $converted_tree;
1540
                }
1672
            }
1541
            $polish .= $converted_tree;
1673
            $polish .= $converted_tree;
1542
        }
1674
        }
-
 
1675
        // if it's a function convert it here (so we can set it's arguments)
-
 
1676
        if (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/",$tree['value']) and
-
 
1677
            !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/',$tree['value']) and
-
 
1678
            !preg_match("/^[A-Ia-i]?[A-Za-z](\d+)\.\.[A-Ia-i]?[A-Za-z](\d+)$/",$tree['value']) and
-
 
1679
            !is_numeric($tree['value']) and
-
 
1680
            !isset($this->ptg[$tree['value']]))
-
 
1681
        {
-
 
1682
            // left subtree for a function is always an array.
-
 
1683
            if ($tree['left'] != '') {
-
 
1684
                $left_tree = $this->toReversePolish($tree['left']);
-
 
1685
            } else {
-
 
1686
                $left_tree = '';
-
 
1687
            }
-
 
1688
            if (PEAR::isError($left_tree)) {
-
 
1689
                return $left_tree;
-
 
1690
            }
-
 
1691
            // add it's left subtree and return.
-
 
1692
            return $left_tree.$this->_convertFunction($tree['value'], $tree['right']);
-
 
1693
        } else {
1543
        $converted_tree = $this->_convert($tree['value']);
1694
            $converted_tree = $this->_convert($tree['value']);
1544
        if($this->isError($converted_tree)) {
1695
            if (PEAR::isError($converted_tree)) {
1545
            return($converted_tree);
1696
                return $converted_tree;
1546
            }
1697
            }
-
 
1698
        }
1547
        $polish .= $converted_tree;
1699
        $polish .= $converted_tree;
1548
        return($polish);
1700
        return $polish;
1549
    }
1701
    }
1550
}
1702
}
1551
?>
1703
?>