Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/pear/Spreadsheet/Excel/Writer/Parser.php
Go to the documentation of this file.
00001 <?php
00028 define('SPREADSHEET_EXCEL_WRITER_ADD', "+");
00029 
00033 define('SPREADSHEET_EXCEL_WRITER_SUB', "-");
00034 
00038 define('SPREADSHEET_EXCEL_WRITER_MUL', "*");
00039 
00043 define('SPREADSHEET_EXCEL_WRITER_DIV', "/");
00044 
00048 define('SPREADSHEET_EXCEL_WRITER_OPEN', "(");
00049 
00053 define('SPREADSHEET_EXCEL_WRITER_CLOSE', ")");
00054 
00058 define('SPREADSHEET_EXCEL_WRITER_COMA', ",");
00059 
00063 define('SPREADSHEET_EXCEL_WRITER_SEMICOLON', ";");
00064 
00068 define('SPREADSHEET_EXCEL_WRITER_GT', ">");
00069 
00073 define('SPREADSHEET_EXCEL_WRITER_LT', "<");
00074 
00078 define('SPREADSHEET_EXCEL_WRITER_LE', "<=");
00079 
00083 define('SPREADSHEET_EXCEL_WRITER_GE', ">=");
00084 
00088 define('SPREADSHEET_EXCEL_WRITER_EQ', "=");
00089 
00093 define('SPREADSHEET_EXCEL_WRITER_NE', "<>");
00094 
00095 
00096 require_once 'PEAR.php';
00097 
00106 class Spreadsheet_Excel_Writer_Parser extends PEAR
00107 {
00112     var $_current_char;
00113 
00118     var $_current_token;
00119 
00124     var $_formula;
00125 
00130     var $_lookahead;
00131 
00136     var $_parse_tree;
00137 
00142     var $_byte_order;
00143 
00148     var $_ext_sheets;
00149 
00154     var $_references;
00155 
00160     var $_BIFF_version;
00161 
00168     function Spreadsheet_Excel_Writer_Parser($byte_order, $biff_version)
00169     {
00170         $this->_current_char  = 0;
00171         $this->_BIFF_version  = $biff_version;
00172         $this->_current_token = '';       // The token we are working on.
00173         $this->_formula       = '';       // The formula to parse.
00174         $this->_lookahead     = '';       // The character ahead of the current char.
00175         $this->_parse_tree    = '';       // The parse tree to be generated.
00176         $this->_initializeHashes();      // Initialize the hashes: ptg's and function's ptg's
00177         $this->_byte_order = $byte_order; // Little Endian or Big Endian
00178         $this->_ext_sheets = array();
00179         $this->_references = array();
00180     }
00181 
00187     function _initializeHashes()
00188     {
00189         // The Excel ptg indices
00190         $this->ptg = array(
00191             'ptgExp'       => 0x01,
00192             'ptgTbl'       => 0x02,
00193             'ptgAdd'       => 0x03,
00194             'ptgSub'       => 0x04,
00195             'ptgMul'       => 0x05,
00196             'ptgDiv'       => 0x06,
00197             'ptgPower'     => 0x07,
00198             'ptgConcat'    => 0x08,
00199             'ptgLT'        => 0x09,
00200             'ptgLE'        => 0x0A,
00201             'ptgEQ'        => 0x0B,
00202             'ptgGE'        => 0x0C,
00203             'ptgGT'        => 0x0D,
00204             'ptgNE'        => 0x0E,
00205             'ptgIsect'     => 0x0F,
00206             'ptgUnion'     => 0x10,
00207             'ptgRange'     => 0x11,
00208             'ptgUplus'     => 0x12,
00209             'ptgUminus'    => 0x13,
00210             'ptgPercent'   => 0x14,
00211             'ptgParen'     => 0x15,
00212             'ptgMissArg'   => 0x16,
00213             'ptgStr'       => 0x17,
00214             'ptgAttr'      => 0x19,
00215             'ptgSheet'     => 0x1A,
00216             'ptgEndSheet'  => 0x1B,
00217             'ptgErr'       => 0x1C,
00218             'ptgBool'      => 0x1D,
00219             'ptgInt'       => 0x1E,
00220             'ptgNum'       => 0x1F,
00221             'ptgArray'     => 0x20,
00222             'ptgFunc'      => 0x21,
00223             'ptgFuncVar'   => 0x22,
00224             'ptgName'      => 0x23,
00225             'ptgRef'       => 0x24,
00226             'ptgArea'      => 0x25,
00227             'ptgMemArea'   => 0x26,
00228             'ptgMemErr'    => 0x27,
00229             'ptgMemNoMem'  => 0x28,
00230             'ptgMemFunc'   => 0x29,
00231             'ptgRefErr'    => 0x2A,
00232             'ptgAreaErr'   => 0x2B,
00233             'ptgRefN'      => 0x2C,
00234             'ptgAreaN'     => 0x2D,
00235             'ptgMemAreaN'  => 0x2E,
00236             'ptgMemNoMemN' => 0x2F,
00237             'ptgNameX'     => 0x39,
00238             'ptgRef3d'     => 0x3A,
00239             'ptgArea3d'    => 0x3B,
00240             'ptgRefErr3d'  => 0x3C,
00241             'ptgAreaErr3d' => 0x3D,
00242             'ptgArrayV'    => 0x40,
00243             'ptgFuncV'     => 0x41,
00244             'ptgFuncVarV'  => 0x42,
00245             'ptgNameV'     => 0x43,
00246             'ptgRefV'      => 0x44,
00247             'ptgAreaV'     => 0x45,
00248             'ptgMemAreaV'  => 0x46,
00249             'ptgMemErrV'   => 0x47,
00250             'ptgMemNoMemV' => 0x48,
00251             'ptgMemFuncV'  => 0x49,
00252             'ptgRefErrV'   => 0x4A,
00253             'ptgAreaErrV'  => 0x4B,
00254             'ptgRefNV'     => 0x4C,
00255             'ptgAreaNV'    => 0x4D,
00256             'ptgMemAreaNV' => 0x4E,
00257             'ptgMemNoMemN' => 0x4F,
00258             'ptgFuncCEV'   => 0x58,
00259             'ptgNameXV'    => 0x59,
00260             'ptgRef3dV'    => 0x5A,
00261             'ptgArea3dV'   => 0x5B,
00262             'ptgRefErr3dV' => 0x5C,
00263             'ptgAreaErr3d' => 0x5D,
00264             'ptgArrayA'    => 0x60,
00265             'ptgFuncA'     => 0x61,
00266             'ptgFuncVarA'  => 0x62,
00267             'ptgNameA'     => 0x63,
00268             'ptgRefA'      => 0x64,
00269             'ptgAreaA'     => 0x65,
00270             'ptgMemAreaA'  => 0x66,
00271             'ptgMemErrA'   => 0x67,
00272             'ptgMemNoMemA' => 0x68,
00273             'ptgMemFuncA'  => 0x69,
00274             'ptgRefErrA'   => 0x6A,
00275             'ptgAreaErrA'  => 0x6B,
00276             'ptgRefNA'     => 0x6C,
00277             'ptgAreaNA'    => 0x6D,
00278             'ptgMemAreaNA' => 0x6E,
00279             'ptgMemNoMemN' => 0x6F,
00280             'ptgFuncCEA'   => 0x78,
00281             'ptgNameXA'    => 0x79,
00282             'ptgRef3dA'    => 0x7A,
00283             'ptgArea3dA'   => 0x7B,
00284             'ptgRefErr3dA' => 0x7C,
00285             'ptgAreaErr3d' => 0x7D
00286             );
00287 
00288         // Thanks to Michael Meeks and Gnumeric for the initial arg values.
00289         //
00290         // The following hash was generated by "function_locale.pl" in the distro.
00291         // Refer to function_locale.pl for non-English function names.
00292         //
00293         // The array elements are as follow:
00294         // ptg:   The Excel function ptg code.
00295         // args:  The number of arguments that the function takes:
00296         //           >=0 is a fixed number of arguments.
00297         //           -1  is a variable  number of arguments.
00298         // class: The reference, value or array class of the function args.
00299         // vol:   The function is volatile.
00300         //
00301         $this->_functions = array(
00302               // function                  ptg  args  class  vol
00303               'COUNT'           => array(   0,   -1,    0,    0 ),
00304               'IF'              => array(   1,   -1,    1,    0 ),
00305               'ISNA'            => array(   2,    1,    1,    0 ),
00306               'ISERROR'         => array(   3,    1,    1,    0 ),
00307               'SUM'             => array(   4,   -1,    0,    0 ),
00308               'AVERAGE'         => array(   5,   -1,    0,    0 ),
00309               'MIN'             => array(   6,   -1,    0,    0 ),
00310               'MAX'             => array(   7,   -1,    0,    0 ),
00311               'ROW'             => array(   8,   -1,    0,    0 ),
00312               'COLUMN'          => array(   9,   -1,    0,    0 ),
00313               'NA'              => array(  10,    0,    0,    0 ),
00314               'NPV'             => array(  11,   -1,    1,    0 ),
00315               'STDEV'           => array(  12,   -1,    0,    0 ),
00316               'DOLLAR'          => array(  13,   -1,    1,    0 ),
00317               'FIXED'           => array(  14,   -1,    1,    0 ),
00318               'SIN'             => array(  15,    1,    1,    0 ),
00319               'COS'             => array(  16,    1,    1,    0 ),
00320               'TAN'             => array(  17,    1,    1,    0 ),
00321               'ATAN'            => array(  18,    1,    1,    0 ),
00322               'PI'              => array(  19,    0,    1,    0 ),
00323               'SQRT'            => array(  20,    1,    1,    0 ),
00324               'EXP'             => array(  21,    1,    1,    0 ),
00325               'LN'              => array(  22,    1,    1,    0 ),
00326               'LOG10'           => array(  23,    1,    1,    0 ),
00327               'ABS'             => array(  24,    1,    1,    0 ),
00328               'INT'             => array(  25,    1,    1,    0 ),
00329               'SIGN'            => array(  26,    1,    1,    0 ),
00330               'ROUND'           => array(  27,    2,    1,    0 ),
00331               'LOOKUP'          => array(  28,   -1,    0,    0 ),
00332               'INDEX'           => array(  29,   -1,    0,    1 ),
00333               'REPT'            => array(  30,    2,    1,    0 ),
00334               'MID'             => array(  31,    3,    1,    0 ),
00335               'LEN'             => array(  32,    1,    1,    0 ),
00336               'VALUE'           => array(  33,    1,    1,    0 ),
00337               'TRUE'            => array(  34,    0,    1,    0 ),
00338               'FALSE'           => array(  35,    0,    1,    0 ),
00339               'AND'             => array(  36,   -1,    0,    0 ),
00340               'OR'              => array(  37,   -1,    0,    0 ),
00341               'NOT'             => array(  38,    1,    1,    0 ),
00342               'MOD'             => array(  39,    2,    1,    0 ),
00343               'DCOUNT'          => array(  40,    3,    0,    0 ),
00344               'DSUM'            => array(  41,    3,    0,    0 ),
00345               'DAVERAGE'        => array(  42,    3,    0,    0 ),
00346               'DMIN'            => array(  43,    3,    0,    0 ),
00347               'DMAX'            => array(  44,    3,    0,    0 ),
00348               'DSTDEV'          => array(  45,    3,    0,    0 ),
00349               'VAR'             => array(  46,   -1,    0,    0 ),
00350               'DVAR'            => array(  47,    3,    0,    0 ),
00351               'TEXT'            => array(  48,    2,    1,    0 ),
00352               'LINEST'          => array(  49,   -1,    0,    0 ),
00353               'TREND'           => array(  50,   -1,    0,    0 ),
00354               'LOGEST'          => array(  51,   -1,    0,    0 ),
00355               'GROWTH'          => array(  52,   -1,    0,    0 ),
00356               'PV'              => array(  56,   -1,    1,    0 ),
00357               'FV'              => array(  57,   -1,    1,    0 ),
00358               'NPER'            => array(  58,   -1,    1,    0 ),
00359               'PMT'             => array(  59,   -1,    1,    0 ),
00360               'RATE'            => array(  60,   -1,    1,    0 ),
00361               'MIRR'            => array(  61,    3,    0,    0 ),
00362               'IRR'             => array(  62,   -1,    0,    0 ),
00363               'RAND'            => array(  63,    0,    1,    1 ),
00364               'MATCH'           => array(  64,   -1,    0,    0 ),
00365               'DATE'            => array(  65,    3,    1,    0 ),
00366               'TIME'            => array(  66,    3,    1,    0 ),
00367               'DAY'             => array(  67,    1,    1,    0 ),
00368               'MONTH'           => array(  68,    1,    1,    0 ),
00369               'YEAR'            => array(  69,    1,    1,    0 ),
00370               'WEEKDAY'         => array(  70,   -1,    1,    0 ),
00371               'HOUR'            => array(  71,    1,    1,    0 ),
00372               'MINUTE'          => array(  72,    1,    1,    0 ),
00373               'SECOND'          => array(  73,    1,    1,    0 ),
00374               'NOW'             => array(  74,    0,    1,    1 ),
00375               'AREAS'           => array(  75,    1,    0,    1 ),
00376               'ROWS'            => array(  76,    1,    0,    1 ),
00377               'COLUMNS'         => array(  77,    1,    0,    1 ),
00378               'OFFSET'          => array(  78,   -1,    0,    1 ),
00379               'SEARCH'          => array(  82,   -1,    1,    0 ),
00380               'TRANSPOSE'       => array(  83,    1,    1,    0 ),
00381               'TYPE'            => array(  86,    1,    1,    0 ),
00382               'ATAN2'           => array(  97,    2,    1,    0 ),
00383               'ASIN'            => array(  98,    1,    1,    0 ),
00384               'ACOS'            => array(  99,    1,    1,    0 ),
00385               'CHOOSE'          => array( 100,   -1,    1,    0 ),
00386               'HLOOKUP'         => array( 101,   -1,    0,    0 ),
00387               'VLOOKUP'         => array( 102,   -1,    0,    0 ),
00388               'ISREF'           => array( 105,    1,    0,    0 ),
00389               'LOG'             => array( 109,   -1,    1,    0 ),
00390               'CHAR'            => array( 111,    1,    1,    0 ),
00391               'LOWER'           => array( 112,    1,    1,    0 ),
00392               'UPPER'           => array( 113,    1,    1,    0 ),
00393               'PROPER'          => array( 114,    1,    1,    0 ),
00394               'LEFT'            => array( 115,   -1,    1,    0 ),
00395               'RIGHT'           => array( 116,   -1,    1,    0 ),
00396               'EXACT'           => array( 117,    2,    1,    0 ),
00397               'TRIM'            => array( 118,    1,    1,    0 ),
00398               'REPLACE'         => array( 119,    4,    1,    0 ),
00399               'SUBSTITUTE'      => array( 120,   -1,    1,    0 ),
00400               'CODE'            => array( 121,    1,    1,    0 ),
00401               'FIND'            => array( 124,   -1,    1,    0 ),
00402               'CELL'            => array( 125,   -1,    0,    1 ),
00403               'ISERR'           => array( 126,    1,    1,    0 ),
00404               'ISTEXT'          => array( 127,    1,    1,    0 ),
00405               'ISNUMBER'        => array( 128,    1,    1,    0 ),
00406               'ISBLANK'         => array( 129,    1,    1,    0 ),
00407               'T'               => array( 130,    1,    0,    0 ),
00408               'N'               => array( 131,    1,    0,    0 ),
00409               'DATEVALUE'       => array( 140,    1,    1,    0 ),
00410               'TIMEVALUE'       => array( 141,    1,    1,    0 ),
00411               'SLN'             => array( 142,    3,    1,    0 ),
00412               'SYD'             => array( 143,    4,    1,    0 ),
00413               'DDB'             => array( 144,   -1,    1,    0 ),
00414               'INDIRECT'        => array( 148,   -1,    1,    1 ),
00415               'CALL'            => array( 150,   -1,    1,    0 ),
00416               'CLEAN'           => array( 162,    1,    1,    0 ),
00417               'MDETERM'         => array( 163,    1,    2,    0 ),
00418               'MINVERSE'        => array( 164,    1,    2,    0 ),
00419               'MMULT'           => array( 165,    2,    2,    0 ),
00420               'IPMT'            => array( 167,   -1,    1,    0 ),
00421               'PPMT'            => array( 168,   -1,    1,    0 ),
00422               'COUNTA'          => array( 169,   -1,    0,    0 ),
00423               'PRODUCT'         => array( 183,   -1,    0,    0 ),
00424               'FACT'            => array( 184,    1,    1,    0 ),
00425               'DPRODUCT'        => array( 189,    3,    0,    0 ),
00426               'ISNONTEXT'       => array( 190,    1,    1,    0 ),
00427               'STDEVP'          => array( 193,   -1,    0,    0 ),
00428               'VARP'            => array( 194,   -1,    0,    0 ),
00429               'DSTDEVP'         => array( 195,    3,    0,    0 ),
00430               'DVARP'           => array( 196,    3,    0,    0 ),
00431               'TRUNC'           => array( 197,   -1,    1,    0 ),
00432               'ISLOGICAL'       => array( 198,    1,    1,    0 ),
00433               'DCOUNTA'         => array( 199,    3,    0,    0 ),
00434               'ROUNDUP'         => array( 212,    2,    1,    0 ),
00435               'ROUNDDOWN'       => array( 213,    2,    1,    0 ),
00436               'RANK'            => array( 216,   -1,    0,    0 ),
00437               'ADDRESS'         => array( 219,   -1,    1,    0 ),
00438               'DAYS360'         => array( 220,   -1,    1,    0 ),
00439               'TODAY'           => array( 221,    0,    1,    1 ),
00440               'VDB'             => array( 222,   -1,    1,    0 ),
00441               'MEDIAN'          => array( 227,   -1,    0,    0 ),
00442               'SUMPRODUCT'      => array( 228,   -1,    2,    0 ),
00443               'SINH'            => array( 229,    1,    1,    0 ),
00444               'COSH'            => array( 230,    1,    1,    0 ),
00445               'TANH'            => array( 231,    1,    1,    0 ),
00446               'ASINH'           => array( 232,    1,    1,    0 ),
00447               'ACOSH'           => array( 233,    1,    1,    0 ),
00448               'ATANH'           => array( 234,    1,    1,    0 ),
00449               'DGET'            => array( 235,    3,    0,    0 ),
00450               'INFO'            => array( 244,    1,    1,    1 ),
00451               'DB'              => array( 247,   -1,    1,    0 ),
00452               'FREQUENCY'       => array( 252,    2,    0,    0 ),
00453               'ERROR.TYPE'      => array( 261,    1,    1,    0 ),
00454               'REGISTER.ID'     => array( 267,   -1,    1,    0 ),
00455               'AVEDEV'          => array( 269,   -1,    0,    0 ),
00456               'BETADIST'        => array( 270,   -1,    1,    0 ),
00457               'GAMMALN'         => array( 271,    1,    1,    0 ),
00458               'BETAINV'         => array( 272,   -1,    1,    0 ),
00459               'BINOMDIST'       => array( 273,    4,    1,    0 ),
00460               'CHIDIST'         => array( 274,    2,    1,    0 ),
00461               'CHIINV'          => array( 275,    2,    1,    0 ),
00462               'COMBIN'          => array( 276,    2,    1,    0 ),
00463               'CONFIDENCE'      => array( 277,    3,    1,    0 ),
00464               'CRITBINOM'       => array( 278,    3,    1,    0 ),
00465               'EVEN'            => array( 279,    1,    1,    0 ),
00466               'EXPONDIST'       => array( 280,    3,    1,    0 ),
00467               'FDIST'           => array( 281,    3,    1,    0 ),
00468               'FINV'            => array( 282,    3,    1,    0 ),
00469               'FISHER'          => array( 283,    1,    1,    0 ),
00470               'FISHERINV'       => array( 284,    1,    1,    0 ),
00471               'FLOOR'           => array( 285,    2,    1,    0 ),
00472               'GAMMADIST'       => array( 286,    4,    1,    0 ),
00473               'GAMMAINV'        => array( 287,    3,    1,    0 ),
00474               'CEILING'         => array( 288,    2,    1,    0 ),
00475               'HYPGEOMDIST'     => array( 289,    4,    1,    0 ),
00476               'LOGNORMDIST'     => array( 290,    3,    1,    0 ),
00477               'LOGINV'          => array( 291,    3,    1,    0 ),
00478               'NEGBINOMDIST'    => array( 292,    3,    1,    0 ),
00479               'NORMDIST'        => array( 293,    4,    1,    0 ),
00480               'NORMSDIST'       => array( 294,    1,    1,    0 ),
00481               'NORMINV'         => array( 295,    3,    1,    0 ),
00482               'NORMSINV'        => array( 296,    1,    1,    0 ),
00483               'STANDARDIZE'     => array( 297,    3,    1,    0 ),
00484               'ODD'             => array( 298,    1,    1,    0 ),
00485               'PERMUT'          => array( 299,    2,    1,    0 ),
00486               'POISSON'         => array( 300,    3,    1,    0 ),
00487               'TDIST'           => array( 301,    3,    1,    0 ),
00488               'WEIBULL'         => array( 302,    4,    1,    0 ),
00489               'SUMXMY2'         => array( 303,    2,    2,    0 ),
00490               'SUMX2MY2'        => array( 304,    2,    2,    0 ),
00491               'SUMX2PY2'        => array( 305,    2,    2,    0 ),
00492               'CHITEST'         => array( 306,    2,    2,    0 ),
00493               'CORREL'          => array( 307,    2,    2,    0 ),
00494               'COVAR'           => array( 308,    2,    2,    0 ),
00495               'FORECAST'        => array( 309,    3,    2,    0 ),
00496               'FTEST'           => array( 310,    2,    2,    0 ),
00497               'INTERCEPT'       => array( 311,    2,    2,    0 ),
00498               'PEARSON'         => array( 312,    2,    2,    0 ),
00499               'RSQ'             => array( 313,    2,    2,    0 ),
00500               'STEYX'           => array( 314,    2,    2,    0 ),
00501               'SLOPE'           => array( 315,    2,    2,    0 ),
00502               'TTEST'           => array( 316,    4,    2,    0 ),
00503               'PROB'            => array( 317,   -1,    2,    0 ),
00504               'DEVSQ'           => array( 318,   -1,    0,    0 ),
00505               'GEOMEAN'         => array( 319,   -1,    0,    0 ),
00506               'HARMEAN'         => array( 320,   -1,    0,    0 ),
00507               'SUMSQ'           => array( 321,   -1,    0,    0 ),
00508               'KURT'            => array( 322,   -1,    0,    0 ),
00509               'SKEW'            => array( 323,   -1,    0,    0 ),
00510               'ZTEST'           => array( 324,   -1,    0,    0 ),
00511               'LARGE'           => array( 325,    2,    0,    0 ),
00512               'SMALL'           => array( 326,    2,    0,    0 ),
00513               'QUARTILE'        => array( 327,    2,    0,    0 ),
00514               'PERCENTILE'      => array( 328,    2,    0,    0 ),
00515               'PERCENTRANK'     => array( 329,   -1,    0,    0 ),
00516               'MODE'            => array( 330,   -1,    2,    0 ),
00517               'TRIMMEAN'        => array( 331,    2,    0,    0 ),
00518               'TINV'            => array( 332,    2,    1,    0 ),
00519               'CONCATENATE'     => array( 336,   -1,    1,    0 ),
00520               'POWER'           => array( 337,    2,    1,    0 ),
00521               'RADIANS'         => array( 342,    1,    1,    0 ),
00522               'DEGREES'         => array( 343,    1,    1,    0 ),
00523               'SUBTOTAL'        => array( 344,   -1,    0,    0 ),
00524               'SUMIF'           => array( 345,   -1,    0,    0 ),
00525               'COUNTIF'         => array( 346,    2,    0,    0 ),
00526               'COUNTBLANK'      => array( 347,    1,    0,    0 ),
00527               'ROMAN'           => array( 354,   -1,    1,    0 )
00528               );
00529     }
00530 
00539     function _convert($token)
00540     {
00541         if (preg_match("/^\"[^\"]{0,255}\"$/", $token)) {
00542             return $this->_convertString($token);
00543 
00544         } elseif (is_numeric($token)) {
00545             return $this->_convertNumber($token);
00546 
00547         // match references like A1 or $A$1
00548         } elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/',$token)) {
00549             return $this->_convertRef2d($token);
00550 
00551         // match external references like Sheet1!A1 or Sheet1:Sheet2!A1
00552         } elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z](\d+)$/u",$token)) {
00553             return $this->_convertRef3d($token);
00554 
00555         // match external references like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1
00556         } elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z](\d+)$/u",$token)) {
00557             return $this->_convertRef3d($token);
00558 
00559         // match ranges like A1:B2
00560         } elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token)) {
00561             return $this->_convertRange2d($token);
00562 
00563         // match ranges like A1..B2
00564         } elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token)) {
00565             return $this->_convertRange2d($token);
00566 
00567         // match external ranges like Sheet1!A1 or Sheet1:Sheet2!A1:B2
00568         } elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/u",$token)) {
00569             return $this->_convertRange3d($token);
00570 
00571         // match external ranges like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
00572         } elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/u",$token)) {
00573             return $this->_convertRange3d($token);
00574 
00575         // operators (including parentheses)
00576         } elseif (isset($this->ptg[$token])) {
00577             return pack("C", $this->ptg[$token]);
00578 
00579         // commented so argument number can be processed correctly. See toReversePolish().
00580         /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/",$token))
00581         {
00582             return($this->_convertFunction($token,$this->_func_args));
00583         }*/
00584 
00585         // if it's an argument, ignore the token (the argument remains)
00586         } elseif ($token == 'arg') {
00587             return '';
00588         }
00589         // TODO: use real error codes
00590         return $this->raiseError("Unknown token $token");
00591     }
00592 
00599     function _convertNumber($num)
00600     {
00601         // Integer in the range 0..2**16-1
00602         if ((preg_match("/^\d+$/", $num)) and ($num <= 65535)) {
00603             return pack("Cv", $this->ptg['ptgInt'], $num);
00604         } else { // A float
00605             if ($this->_byte_order) { // if it's Big Endian
00606                 $num = strrev($num);
00607             }
00608             return pack("Cd", $this->ptg['ptgNum'], $num);
00609         }
00610     }
00611 
00620     function _convertString($string)
00621     {
00622         // chop away beggining and ending quotes
00623         $string = substr($string, 1, strlen($string) - 2);
00624         if (strlen($string) > 255) {
00625             return $this->raiseError("String is too long");
00626         }
00627 
00628         if ($this->_BIFF_version == 0x0500) {
00629             return pack("CC", $this->ptg['ptgStr'], strlen($string)).$string;
00630         } elseif ($this->_BIFF_version == 0x0600) {
00631             $encoding = 0;   // TODO: Unicode support
00632             return pack("CCC", $this->ptg['ptgStr'], strlen($string), $encoding).$string;
00633         }
00634     }
00635 
00645     function _convertFunction($token, $num_args)
00646     {
00647         $args     = $this->_functions[$token][1];
00648         $volatile = $this->_functions[$token][3];
00649 
00650         // Fixed number of args eg. TIME($i,$j,$k).
00651         if ($args >= 0) {
00652             return pack("Cv", $this->ptg['ptgFuncV'], $this->_functions[$token][0]);
00653         }
00654         // Variable number of args eg. SUM($i,$j,$k, ..).
00655         if ($args == -1) {
00656             return pack("CCv", $this->ptg['ptgFuncVarV'], $num_args, $this->_functions[$token][0]);
00657         }
00658     }
00659 
00666     function _convertRange2d($range)
00667     {
00668         $class = 2; // as far as I know, this is magick.
00669 
00670         // Split the range into 2 cell refs
00671         if (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\:([A-Ia-i]?[A-Za-z])(\d+)$/", $range)) {
00672             list($cell1, $cell2) = explode(':', $range);
00673         } elseif (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\.\.([A-Ia-i]?[A-Za-z])(\d+)$/", $range)) {
00674             list($cell1, $cell2) = explode('..', $range);
00675 
00676         } else {
00677             // TODO: use real error codes
00678             return $this->raiseError("Unknown range separator", 0, PEAR_ERROR_DIE);
00679         }
00680 
00681         // Convert the cell references
00682         $cell_array1 = $this->_cellToPackedRowcol($cell1);
00683         if (PEAR::isError($cell_array1)) {
00684             return $cell_array1;
00685         }
00686         list($row1, $col1) = $cell_array1;
00687         $cell_array2 = $this->_cellToPackedRowcol($cell2);
00688         if (PEAR::isError($cell_array2)) {
00689             return $cell_array2;
00690         }
00691         list($row2, $col2) = $cell_array2;
00692 
00693         // The ptg value depends on the class of the ptg.
00694         if ($class == 0) {
00695             $ptgArea = pack("C", $this->ptg['ptgArea']);
00696         } elseif ($class == 1) {
00697             $ptgArea = pack("C", $this->ptg['ptgAreaV']);
00698         } elseif ($class == 2) {
00699             $ptgArea = pack("C", $this->ptg['ptgAreaA']);
00700         } else {
00701             // TODO: use real error codes
00702             return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
00703         }
00704         return $ptgArea . $row1 . $row2 . $col1. $col2;
00705     }
00706 
00715     function _convertRange3d($token)
00716     {
00717         $class = 2; // as far as I know, this is magick.
00718 
00719         // Split the ref at the ! symbol
00720         list($ext_ref, $range) = explode('!', $token);
00721 
00722         // Convert the external reference part (different for BIFF8)
00723         if ($this->_BIFF_version == 0x0500) {
00724             $ext_ref = $this->_packExtRef($ext_ref);
00725             if (PEAR::isError($ext_ref)) {
00726                 return $ext_ref;
00727             }
00728         } elseif ($this->_BIFF_version == 0x0600) {
00729              $ext_ref = $this->_getRefIndex($ext_ref);
00730              if (PEAR::isError($ext_ref)) {
00731                  return $ext_ref;
00732              }
00733         }
00734 
00735         // Split the range into 2 cell refs
00736         list($cell1, $cell2) = explode(':', $range);
00737 
00738         // Convert the cell references
00739         if (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/", $cell1)) {
00740             $cell_array1 = $this->_cellToPackedRowcol($cell1);
00741             if (PEAR::isError($cell_array1)) {
00742                 return $cell_array1;
00743             }
00744             list($row1, $col1) = $cell_array1;
00745             $cell_array2 = $this->_cellToPackedRowcol($cell2);
00746             if (PEAR::isError($cell_array2)) {
00747                 return $cell_array2;
00748             }
00749             list($row2, $col2) = $cell_array2;
00750         } else { // It's a rows range (like 26:27)
00751              $cells_array = $this->_rangeToPackedRange($cell1.':'.$cell2);
00752              if (PEAR::isError($cells_array)) {
00753                  return $cells_array;
00754              }
00755              list($row1, $col1, $row2, $col2) = $cells_array;
00756         }
00757 
00758         // The ptg value depends on the class of the ptg.
00759         if ($class == 0) {
00760             $ptgArea = pack("C", $this->ptg['ptgArea3d']);
00761         } elseif ($class == 1) {
00762             $ptgArea = pack("C", $this->ptg['ptgArea3dV']);
00763         } elseif ($class == 2) {
00764             $ptgArea = pack("C", $this->ptg['ptgArea3dA']);
00765         } else {
00766             return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
00767         }
00768 
00769         return $ptgArea . $ext_ref . $row1 . $row2 . $col1. $col2;
00770     }
00771 
00779     function _convertRef2d($cell)
00780     {
00781         $class = 2; // as far as I know, this is magick.
00782 
00783         // Convert the cell reference
00784         $cell_array = $this->_cellToPackedRowcol($cell);
00785         if (PEAR::isError($cell_array)) {
00786             return $cell_array;
00787         }
00788         list($row, $col) = $cell_array;
00789 
00790         // The ptg value depends on the class of the ptg.
00791         if ($class == 0) {
00792             $ptgRef = pack("C", $this->ptg['ptgRef']);
00793         } elseif ($class == 1) {
00794             $ptgRef = pack("C", $this->ptg['ptgRefV']);
00795         } elseif ($class == 2) {
00796             $ptgRef = pack("C", $this->ptg['ptgRefA']);
00797         } else {
00798             // TODO: use real error codes
00799             return $this->raiseError("Unknown class $class");
00800         }
00801         return $ptgRef.$row.$col;
00802     }
00803 
00812     function _convertRef3d($cell)
00813     {
00814         $class = 2; // as far as I know, this is magick.
00815 
00816         // Split the ref at the ! symbol
00817         list($ext_ref, $cell) = explode('!', $cell);
00818 
00819         // Convert the external reference part (different for BIFF8)
00820         if ($this->_BIFF_version == 0x0500) {
00821             $ext_ref = $this->_packExtRef($ext_ref);
00822             if (PEAR::isError($ext_ref)) {
00823                 return $ext_ref;
00824             }
00825         } elseif ($this->_BIFF_version == 0x0600) {
00826             $ext_ref = $this->_getRefIndex($ext_ref);
00827             if (PEAR::isError($ext_ref)) {
00828                 return $ext_ref;
00829             }
00830         }
00831 
00832         // Convert the cell reference part
00833         list($row, $col) = $this->_cellToPackedRowcol($cell);
00834 
00835         // The ptg value depends on the class of the ptg.
00836         if ($class == 0) {
00837             $ptgRef = pack("C", $this->ptg['ptgRef3d']);
00838         } elseif ($class == 1) {
00839             $ptgRef = pack("C", $this->ptg['ptgRef3dV']);
00840         } elseif ($class == 2) {
00841             $ptgRef = pack("C", $this->ptg['ptgRef3dA']);
00842         } else {
00843             return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
00844         }
00845 
00846         return $ptgRef . $ext_ref. $row . $col;
00847     }
00848 
00857     function _packExtRef($ext_ref)
00858     {
00859         $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
00860         $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
00861 
00862         // Check if there is a sheet range eg., Sheet1:Sheet2.
00863         if (preg_match("/:/", $ext_ref)) {
00864             list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
00865 
00866             $sheet1 = $this->_getSheetIndex($sheet_name1);
00867             if ($sheet1 == -1) {
00868                 return $this->raiseError("Unknown sheet name $sheet_name1 in formula");
00869             }
00870             $sheet2 = $this->_getSheetIndex($sheet_name2);
00871             if ($sheet2 == -1) {
00872                 return $this->raiseError("Unknown sheet name $sheet_name2 in formula");
00873             }
00874 
00875             // Reverse max and min sheet numbers if necessary
00876             if ($sheet1 > $sheet2) {
00877                 list($sheet1, $sheet2) = array($sheet2, $sheet1);
00878             }
00879         } else { // Single sheet name only.
00880             $sheet1 = $this->_getSheetIndex($ext_ref);
00881             if ($sheet1 == -1) {
00882                 return $this->raiseError("Unknown sheet name $ext_ref in formula");
00883             }
00884             $sheet2 = $sheet1;
00885         }
00886 
00887         // References are stored relative to 0xFFFF.
00888         $offset = -1 - $sheet1;
00889 
00890         return pack('vdvv', $offset, 0x00, $sheet1, $sheet2);
00891     }
00892 
00903     function _getRefIndex($ext_ref)
00904     {
00905         $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
00906         $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
00907 
00908         // Check if there is a sheet range eg., Sheet1:Sheet2.
00909         if (preg_match("/:/", $ext_ref)) {
00910             list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
00911 
00912             $sheet1 = $this->_getSheetIndex($sheet_name1);
00913             if ($sheet1 == -1) {
00914                 return $this->raiseError("Unknown sheet name $sheet_name1 in formula");
00915             }
00916             $sheet2 = $this->_getSheetIndex($sheet_name2);
00917             if ($sheet2 == -1) {
00918                 return $this->raiseError("Unknown sheet name $sheet_name2 in formula");
00919             }
00920 
00921             // Reverse max and min sheet numbers if necessary
00922             if ($sheet1 > $sheet2) {
00923                 list($sheet1, $sheet2) = array($sheet2, $sheet1);
00924             }
00925         } else { // Single sheet name only.
00926             $sheet1 = $this->_getSheetIndex($ext_ref);
00927             if ($sheet1 == -1) {
00928                 return $this->raiseError("Unknown sheet name $ext_ref in formula");
00929             }
00930             $sheet2 = $sheet1;
00931         }
00932 
00933         // assume all references belong to this document
00934         $supbook_index = 0x00;
00935         $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
00936         $total_references = count($this->_references);
00937         $index = -1;
00938         for ($i = 0; $i < $total_references; $i++) {
00939             if ($ref == $this->_references[$i]) {
00940                 $index = $i;
00941                 break;
00942             }
00943         }
00944         // if REF was not found add it to references array
00945         if ($index == -1) {
00946             $this->_references[$total_references] = $ref;
00947             $index = $total_references;
00948         }
00949 
00950         return pack('v', $index);
00951     }
00952 
00961     function _getSheetIndex($sheet_name)
00962     {
00963         if (!isset($this->_ext_sheets[$sheet_name])) {
00964             return -1;
00965         } else {
00966             return $this->_ext_sheets[$sheet_name];
00967         }
00968     }
00969 
00980     function setExtSheet($name, $index)
00981     {
00982         $this->_ext_sheets[$name] = $index;
00983     }
00984 
00992     function _cellToPackedRowcol($cell)
00993     {
00994         $cell = strtoupper($cell);
00995         list($row, $col, $row_rel, $col_rel) = $this->_cellToRowcol($cell);
00996         if ($col >= 256) {
00997             return $this->raiseError("Column in: $cell greater than 255");
00998         }
00999         // FIXME: change for BIFF8
01000         if ($row >= 16384) {
01001             return $this->raiseError("Row in: $cell greater than 16384 ");
01002         }
01003 
01004         // Set the high bits to indicate if row or col are relative.
01005         if ($this->_BIFF_version == 0x0500) {
01006             $row    |= $col_rel << 14;
01007             $row    |= $row_rel << 15;
01008             $col     = pack('C', $col);
01009         } elseif ($this->_BIFF_version == 0x0600) {
01010             $col    |= $col_rel << 14;
01011             $col    |= $row_rel << 15;
01012             $col     = pack('v', $col);
01013         }
01014         $row     = pack('v', $row);
01015 
01016         return array($row, $col);
01017     }
01018 
01027     function _rangeToPackedRange($range)
01028     {
01029         preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match);
01030         // return absolute rows if there is a $ in the ref
01031         $row1_rel = empty($match[1]) ? 1 : 0;
01032         $row1     = $match[2];
01033         $row2_rel = empty($match[3]) ? 1 : 0;
01034         $row2     = $match[4];
01035         // Convert 1-index to zero-index
01036         $row1--;
01037         $row2--;
01038         // Trick poor inocent Excel
01039         $col1 = 0;
01040         $col2 = 16383; // FIXME: maximum possible value for Excel 5 (change this!!!)
01041 
01042         // FIXME: this changes for BIFF8
01043         if (($row1 >= 16384) or ($row2 >= 16384)) {
01044             return $this->raiseError("Row in: $range greater than 16384 ");
01045         }
01046 
01047         // Set the high bits to indicate if rows are relative.
01048         if ($this->_BIFF_version == 0x0500) {
01049             $row1    |= $row1_rel << 14; // FIXME: probably a bug
01050             $row2    |= $row2_rel << 15;
01051             $col1     = pack('C', $col1);
01052             $col2     = pack('C', $col2);
01053         } elseif ($this->_BIFF_version == 0x0600) {
01054             $col1    |= $row1_rel << 15;
01055             $col2    |= $row2_rel << 15;
01056             $col1     = pack('v', $col1);
01057             $col2     = pack('v', $col2);
01058         }
01059         $row1     = pack('v', $row1);
01060         $row2     = pack('v', $row2);
01061 
01062         return array($row1, $col1, $row2, $col2);
01063     }
01064 
01074     function _cellToRowcol($cell)
01075     {
01076         preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/',$cell,$match);
01077         // return absolute column if there is a $ in the ref
01078         $col_rel = empty($match[1]) ? 1 : 0;
01079         $col_ref = $match[2];
01080         $row_rel = empty($match[3]) ? 1 : 0;
01081         $row     = $match[4];
01082 
01083         // Convert base26 column string to a number.
01084         $expn   = strlen($col_ref) - 1;
01085         $col    = 0;
01086         $col_ref_length = strlen($col_ref);
01087         for ($i = 0; $i < $col_ref_length; $i++) {
01088             $col += (ord($col_ref{$i}) - ord('A') + 1) * pow(26, $expn);
01089             $expn--;
01090         }
01091 
01092         // Convert 1-index to zero-index
01093         $row--;
01094         $col--;
01095 
01096         return array($row, $col, $row_rel, $col_rel);
01097     }
01098 
01104     function _advance()
01105     {
01106         $i = $this->_current_char;
01107         $formula_length = strlen($this->_formula);
01108         // eat up white spaces
01109         if ($i < $formula_length) {
01110             while ($this->_formula{$i} == " ") {
01111                 $i++;
01112             }
01113 
01114             if ($i < ($formula_length - 1)) {
01115                 $this->_lookahead = $this->_formula{$i+1};
01116             }
01117             $token = '';
01118         }
01119 
01120         while ($i < $formula_length) {
01121             $token .= $this->_formula{$i};
01122             if ($i < ($formula_length - 1)) {
01123                 $this->_lookahead = $this->_formula{$i+1};
01124             } else {
01125                 $this->_lookahead = '';
01126             }
01127 
01128             if ($this->_match($token) != '') {
01129                 //if ($i < strlen($this->_formula) - 1) {
01130                 //    $this->_lookahead = $this->_formula{$i+1};
01131                 //}
01132                 $this->_current_char = $i + 1;
01133                 $this->_current_token = $token;
01134                 return 1;
01135             }
01136 
01137             if ($i < ($formula_length - 2)) {
01138                 $this->_lookahead = $this->_formula{$i+2};
01139             } else { // if we run out of characters _lookahead becomes empty
01140                 $this->_lookahead = '';
01141             }
01142             $i++;
01143         }
01144         //die("Lexical error ".$this->_current_char);
01145     }
01146 
01154     function _match($token)
01155     {
01156         switch($token) {
01157             case SPREADSHEET_EXCEL_WRITER_ADD:
01158                 return $token;
01159                 break;
01160             case SPREADSHEET_EXCEL_WRITER_SUB:
01161                 return $token;
01162                 break;
01163             case SPREADSHEET_EXCEL_WRITER_MUL:
01164                 return $token;
01165                 break;
01166             case SPREADSHEET_EXCEL_WRITER_DIV:
01167                 return $token;
01168                 break;
01169             case SPREADSHEET_EXCEL_WRITER_OPEN:
01170                 return $token;
01171                 break;
01172             case SPREADSHEET_EXCEL_WRITER_CLOSE:
01173                 return $token;
01174                 break;
01175             case SPREADSHEET_EXCEL_WRITER_COMA:
01176                 return $token;
01177                 break;
01178             case SPREADSHEET_EXCEL_WRITER_SEMICOLON:
01179                 return $token;
01180                 break;
01181             case SPREADSHEET_EXCEL_WRITER_GT:
01182                 if ($this->_lookahead == '=') { // it's a GE token
01183                     break;
01184                 }
01185                 return $token;
01186                 break;
01187             case SPREADSHEET_EXCEL_WRITER_LT:
01188                 // it's a LE or a NE token
01189                 if (($this->_lookahead == '=') or ($this->_lookahead == '>')) {
01190                     break;
01191                 }
01192                 return $token;
01193                 break;
01194             case SPREADSHEET_EXCEL_WRITER_GE:
01195                 return $token;
01196                 break;
01197             case SPREADSHEET_EXCEL_WRITER_LE:
01198                 return $token;
01199                 break;
01200             case SPREADSHEET_EXCEL_WRITER_EQ:
01201                 return $token;
01202                 break;
01203             case SPREADSHEET_EXCEL_WRITER_NE:
01204                 return $token;
01205                 break;
01206             default:
01207                 // if it's a reference
01208                 if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$token) and
01209                    !preg_match("/[0-9]/",$this->_lookahead) and
01210                    ($this->_lookahead != ':') and ($this->_lookahead != '.') and
01211                    ($this->_lookahead != '!'))
01212                 {
01213                     return $token;
01214                 }
01215                 // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
01216                 elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/u",$token) and
01217                        !preg("/[0-9]/",$this->_lookahead) and
01218                        ($this->_lookahead != ':') and ($this->_lookahead != '.'))
01219                 {
01220                     return $token;
01221                 }
01222                 // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1)
01223                 elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/u",$token) and
01224                        !preg("/[0-9]/",$this->_lookahead) and
01225                        ($this->_lookahead != ':') and ($this->_lookahead != '.'))
01226                 {
01227                     return $token;
01228                 }
01229                 // if it's a range (A1:A2)
01230                 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and 
01231                        !preg_match("/[0-9]/",$this->_lookahead))
01232                 {
01233                     return $token;
01234                 }
01235                 // if it's a range (A1..A2)
01236                 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and 
01237                        !preg_match("/[0-9]/",$this->_lookahead))
01238                 {
01239                     return $token;
01240                 }
01241                 // If it's an external range like Sheet1!A1 or Sheet1:Sheet2!A1:B2
01242                 elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$token) and
01243                        !preg_match("/[0-9]/",$this->_lookahead))
01244                 {
01245                     return $token;
01246                 }
01247                 // If it's an external range like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
01248                 elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$token) and
01249                        !preg_match("/[0-9]/",$this->_lookahead))
01250                 {
01251                     return $token;
01252                 }
01253                 // If it's a number (check that it's not a sheet name or range)
01254                 elseif (is_numeric($token) and 
01255                         (!is_numeric($token.$this->_lookahead) or ($this->_lookahead == '')) and
01256                         ($this->_lookahead != '!') and ($this->_lookahead != ':'))
01257                 {
01258                     return $token;
01259                 }
01260                 // If it's a string (of maximum 255 characters)
01261                 elseif (preg_match("/^\"[^\"]{0,255}\"$/",$token))
01262                 {
01263                     return $token;
01264                 }
01265                 // if it's a function call
01266                 elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i",$token) and ($this->_lookahead == "("))
01267                 {
01268                     return $token;
01269                 }
01270                 return '';
01271         }
01272     }
01273 
01282     function parse($formula)
01283     {
01284         $this->_current_char = 0;
01285         $this->_formula      = $formula;
01286         $this->_lookahead    = $formula{1};
01287         $this->_advance();
01288         $this->_parse_tree   = $this->_condition();
01289         if (PEAR::isError($this->_parse_tree)) {
01290             return $this->_parse_tree;
01291         }
01292         return true;
01293     }
01294 
01302     function _condition()
01303     {
01304         $result = $this->_expression();
01305         if (PEAR::isError($result)) {
01306             return $result;
01307         }
01308         if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LT) {
01309             $this->_advance();
01310             $result2 = $this->_expression();
01311             if (PEAR::isError($result2)) {
01312                 return $result2;
01313             }
01314             $result = $this->_createTree('ptgLT', $result, $result2);
01315         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GT) {
01316             $this->_advance();
01317             $result2 = $this->_expression();
01318             if (PEAR::isError($result2)) {
01319                 return $result2;
01320             }
01321             $result = $this->_createTree('ptgGT', $result, $result2);
01322         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LE) {
01323             $this->_advance();
01324             $result2 = $this->_expression();
01325             if (PEAR::isError($result2)) {
01326                 return $result2;
01327             }
01328             $result = $this->_createTree('ptgLE', $result, $result2);
01329         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GE) {
01330             $this->_advance();
01331             $result2 = $this->_expression();
01332             if (PEAR::isError($result2)) {
01333                 return $result2;
01334             }
01335             $result = $this->_createTree('ptgGE', $result, $result2);
01336         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_EQ) {
01337             $this->_advance();
01338             $result2 = $this->_expression();
01339             if (PEAR::isError($result2)) {
01340                 return $result2;
01341             }
01342             $result = $this->_createTree('ptgEQ', $result, $result2);
01343         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_NE) {
01344             $this->_advance();
01345             $result2 = $this->_expression();
01346             if (PEAR::isError($result2)) {
01347                 return $result2;
01348             }
01349             $result = $this->_createTree('ptgNE', $result, $result2);
01350         }
01351         return $result;
01352     }
01353 
01363     function _expression()
01364     {
01365         // If it's a string return a string node
01366         if (preg_match("/^\"[^\"]{0,255}\"$/", $this->_current_token)) {
01367             $result = $this->_createTree($this->_current_token, '', '');
01368             $this->_advance();
01369             return $result;
01370         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB) {
01371             // catch "-" Term
01372             $this->_advance();
01373             $result2 = $this->_expression();
01374             $result = $this->_createTree('ptgUminus', $result2, '');
01375             return $result;
01376         }
01377         $result = $this->_term();
01378         if (PEAR::isError($result)) {
01379             return $result;
01380         }
01381         while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD) or
01382                ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB)) {
01383         
01384             if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD) {
01385                 $this->_advance();
01386                 $result2 = $this->_term();
01387                 if (PEAR::isError($result2)) {
01388                     return $result2;
01389                 }
01390                 $result = $this->_createTree('ptgAdd', $result, $result2);
01391             } else {
01392                 $this->_advance();
01393                 $result2 = $this->_term();
01394                 if (PEAR::isError($result2)) {
01395                     return $result2;
01396                 }
01397                 $result = $this->_createTree('ptgSub', $result, $result2);
01398             }
01399         }
01400         return $result;
01401     }
01402 
01411     function _parenthesizedExpression()
01412     {
01413         $result = $this->_createTree('ptgParen', $this->_expression(), '');
01414         return $result;
01415     }
01416 
01424     function _term()
01425     {
01426         $result = $this->_fact();
01427         if (PEAR::isError($result)) {
01428             return $result;
01429         }
01430         while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL) or
01431                ($this->_current_token == SPREADSHEET_EXCEL_WRITER_DIV)) {
01432         
01433             if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL) {
01434                 $this->_advance();
01435                 $result2 = $this->_fact();
01436                 if (PEAR::isError($result2)) {
01437                     return $result2;
01438                 }
01439                 $result = $this->_createTree('ptgMul', $result, $result2);
01440             } else {
01441                 $this->_advance();
01442                 $result2 = $this->_fact();
01443                 if (PEAR::isError($result2)) {
01444                     return $result2;
01445                 }
01446                 $result = $this->_createTree('ptgDiv', $result, $result2);
01447             }
01448         }
01449         return $result;
01450     }
01451 
01463     function _fact()
01464     {
01465         if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_OPEN) {
01466             $this->_advance();         // eat the "("
01467             $result = $this->_parenthesizedExpression();
01468             if ($this->_current_token != SPREADSHEET_EXCEL_WRITER_CLOSE) {
01469                 return $this->raiseError("')' token expected.");
01470             }
01471             $this->_advance();         // eat the ")"
01472             return $result;
01473         }
01474         // if it's a reference
01475         if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$this->_current_token))
01476         {
01477             $result = $this->_createTree($this->_current_token, '', '');
01478             $this->_advance();
01479             return $result;
01480         }
01481         // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
01482         elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/u",$this->_current_token))
01483         {
01484             $result = $this->_createTree($this->_current_token, '', '');
01485             $this->_advance();
01486             return $result;
01487         }
01488         // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1)
01489         elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/u",$this->_current_token))
01490         {
01491             $result = $this->_createTree($this->_current_token, '', '');
01492             $this->_advance();
01493             return $result;
01494         }
01495         // if it's a range
01496         elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token) or 
01497                 preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token))
01498         {
01499             $result = $this->_current_token;
01500             $this->_advance();
01501             return $result;
01502         }
01503         // If it's an external range (Sheet1!A1 or Sheet1!A1:B2)
01504         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))
01505         {
01506             $result = $this->_current_token;
01507             $this->_advance();
01508             return $result;
01509         }
01510         // If it's an external range ('Sheet1'!A1 or 'Sheet1'!A1:B2)
01511         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))
01512         {
01513             $result = $this->_current_token;
01514             $this->_advance();
01515             return $result;
01516         }
01517         elseif (is_numeric($this->_current_token))
01518         {
01519             $result = $this->_createTree($this->_current_token, '', '');
01520             $this->_advance();
01521             return $result;
01522         }
01523         // if it's a function call
01524         elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/",$this->_current_token))
01525         {
01526             $result = $this->_func();
01527             return $result;
01528         }
01529         return $this->raiseError("Syntax error: ".$this->_current_token.
01530                                  ", lookahead: ".$this->_lookahead.
01531                                  ", current char: ".$this->_current_char);
01532     }
01533 
01541     function _func()
01542     {
01543         $num_args = 0; // number of arguments received
01544         $function = strtoupper($this->_current_token);
01545         $result   = ''; // initialize result
01546         $this->_advance();
01547         $this->_advance();         // eat the "("
01548         while ($this->_current_token != ')') {
01549         
01550             if ($num_args > 0) {
01551                 if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_COMA or
01552                     $this->_current_token == SPREADSHEET_EXCEL_WRITER_SEMICOLON)
01553                 {
01554                     $this->_advance();  // eat the "," or ";"
01555                 } else {
01556                     return $this->raiseError("Syntax error: comma expected in ".
01557                                       "function $function, arg #{$num_args}");
01558                 }
01559                 $result2 = $this->_condition();
01560                 if (PEAR::isError($result2)) {
01561                     return $result2;
01562                 }
01563                 $result = $this->_createTree('arg', $result, $result2);
01564             } else { // first argument
01565                 $result2 = $this->_condition();
01566                 if (PEAR::isError($result2)) {
01567                     return $result2;
01568                 }
01569                 $result = $this->_createTree('arg', '', $result2);
01570             }
01571             $num_args++;
01572         }
01573         if (!isset($this->_functions[$function])) {
01574             return $this->raiseError("Function $function() doesn't exist");
01575         }
01576         $args = $this->_functions[$function][1];
01577         // If fixed number of args eg. TIME($i,$j,$k). Check that the number of args is valid.
01578         if (($args >= 0) and ($args != $num_args)) {
01579             return $this->raiseError("Incorrect number of arguments in function $function() ");
01580         }
01581 
01582         $result = $this->_createTree($function, $result, $num_args);
01583         $this->_advance();         // eat the ")"
01584         return $result;
01585     }
01586 
01597     function _createTree($value, $left, $right)
01598     {
01599         return array('value' => $value, 'left' => $left, 'right' => $right);
01600     }
01601 
01629     function toReversePolish($tree = array())
01630     {
01631         $polish = ""; // the string we are going to return
01632         if (empty($tree)) { // If it's the first call use _parse_tree
01633             $tree = $this->_parse_tree;
01634         }
01635         if (is_array($tree['left'])) {
01636             $converted_tree = $this->toReversePolish($tree['left']);
01637             if (PEAR::isError($converted_tree)) {
01638                 return $converted_tree;
01639             }
01640             $polish .= $converted_tree;
01641         } elseif ($tree['left'] != '') { // It's a final node
01642             $converted_tree = $this->_convert($tree['left']);
01643             if (PEAR::isError($converted_tree)) {
01644                 return $converted_tree;
01645             }
01646             $polish .= $converted_tree;
01647         }
01648         if (is_array($tree['right'])) {
01649             $converted_tree = $this->toReversePolish($tree['right']);
01650             if (PEAR::isError($converted_tree)) {
01651                 return $converted_tree;
01652             }
01653             $polish .= $converted_tree;
01654         } elseif ($tree['right'] != '') { // It's a final node
01655             $converted_tree = $this->_convert($tree['right']);
01656             if (PEAR::isError($converted_tree)) {
01657                 return $converted_tree;
01658             }
01659             $polish .= $converted_tree;
01660         }
01661         // if it's a function convert it here (so we can set it's arguments)
01662         if (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/",$tree['value']) and
01663             !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/',$tree['value']) and
01664             !preg_match("/^[A-Ia-i]?[A-Za-z](\d+)\.\.[A-Ia-i]?[A-Za-z](\d+)$/",$tree['value']) and
01665             !is_numeric($tree['value']) and
01666             !isset($this->ptg[$tree['value']]))
01667         {
01668             // left subtree for a function is always an array.
01669             if ($tree['left'] != '') {
01670                 $left_tree = $this->toReversePolish($tree['left']);
01671             } else {
01672                 $left_tree = '';
01673             }
01674             if (PEAR::isError($left_tree)) {
01675                 return $left_tree;
01676             }
01677             // add it's left subtree and return.
01678             return $left_tree.$this->_convertFunction($tree['value'], $tree['right']);
01679         } else {
01680             $converted_tree = $this->_convert($tree['value']);
01681             if (PEAR::isError($converted_tree)) {
01682                 return $converted_tree;
01683             }
01684         }
01685         $polish .= $converted_tree;
01686         return $polish;
01687     }
01688 }
01689 ?>
 All Data Structures Namespaces Files Functions Variables Enumerations