Source for file Parser.php

Documentation is available at Parser.php

  1. <?php
  2. /**
  3. *  Class for parsing Excel formulas
  4. *
  5. *  License Information:
  6. *
  7. *    Spreadsheet_Excel_Writer:  A library for generating Excel Spreadsheets
  8. *    Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
  9. *
  10. *    This library is free software; you can redistribute it and/or
  11. *    modify it under the terms of the GNU Lesser General Public
  12. *    License as published by the Free Software Foundation; either
  13. *    version 2.1 of the License, or (at your option) any later version.
  14. *
  15. *    This library is distributed in the hope that it will be useful,
  16. *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  18. *    Lesser General Public License for more details.
  19. *
  20. *    You should have received a copy of the GNU Lesser General Public
  21. *    License along with this library; if not, write to the Free Software
  22. *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  23. */
  24.  
  25. /**
  26. @const SPREADSHEET_EXCEL_WRITER_ADD token identifier for character "+"
  27. */
  28. define('SPREADSHEET_EXCEL_WRITER_ADD'"+");
  29.  
  30. /**
  31. @const SPREADSHEET_EXCEL_WRITER_SUB token identifier for character "-"
  32. */
  33. define('SPREADSHEET_EXCEL_WRITER_SUB'"-");
  34.  
  35. /**
  36. @const SPREADSHEET_EXCEL_WRITER_MUL token identifier for character "*"
  37. */
  38. define('SPREADSHEET_EXCEL_WRITER_MUL'"*");
  39.  
  40. /**
  41. @const SPREADSHEET_EXCEL_WRITER_DIV token identifier for character "/"
  42. */
  43. define('SPREADSHEET_EXCEL_WRITER_DIV'"/");
  44.  
  45. /**
  46. @const SPREADSHEET_EXCEL_WRITER_OPEN token identifier for character "("
  47. */
  48. define('SPREADSHEET_EXCEL_WRITER_OPEN'"(");
  49.  
  50. /**
  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 ","
  57. */
  58. define('SPREADSHEET_EXCEL_WRITER_COMA'",");
  59.  
  60. /**
  61. @const SPREADSHEET_EXCEL_WRITER_SEMICOLON token identifier for character ";"
  62. */
  63. define('SPREADSHEET_EXCEL_WRITER_SEMICOLON'";");
  64.  
  65. /**
  66. @const SPREADSHEET_EXCEL_WRITER_GT token identifier for character ">"
  67. */
  68. define('SPREADSHEET_EXCEL_WRITER_GT'">");
  69.  
  70. /**
  71. @const SPREADSHEET_EXCEL_WRITER_LT token identifier for character "<"
  72. */
  73. define('SPREADSHEET_EXCEL_WRITER_LT'"<");
  74.  
  75. /**
  76. @const SPREADSHEET_EXCEL_WRITER_LE token identifier for character "<="
  77. */
  78. define('SPREADSHEET_EXCEL_WRITER_LE'"<=");
  79.  
  80. /**
  81. @const SPREADSHEET_EXCEL_WRITER_GE token identifier for character ">="
  82. */
  83. define('SPREADSHEET_EXCEL_WRITER_GE'">=");
  84.  
  85. /**
  86. @const SPREADSHEET_EXCEL_WRITER_EQ token identifier for character "="
  87. */
  88. define('SPREADSHEET_EXCEL_WRITER_EQ'"=");
  89.  
  90. /**
  91. @const SPREADSHEET_EXCEL_WRITER_NE token identifier for character "<>"
  92. */
  93. define('SPREADSHEET_EXCEL_WRITER_NE'"<>");
  94.  
  95. /**
  96. * * @const SPREADSHEET_EXCEL_WRITER_CONCAT token identifier for character "&"
  97. */
  98. define('SPREADSHEET_EXCEL_WRITER_CONCAT'"&");
  99.  
  100. require_once 'PEAR.php';
  101.  
  102. /**
  103. * Class for parsing Excel formulas
  104. *
  105. @author   Xavier Noguer <xnoguer@rezebra.com>
  106. @category FileFormats
  107. @package  Spreadsheet_Excel_Writer
  108. */
  109.  
  110. {
  111.     /**
  112.     * The index of the character we are currently looking at
  113.     * @var integer 
  114.     */
  115.     var $_current_char;
  116.  
  117.     /**
  118.     * The token we are working on.
  119.     * @var string 
  120.     */
  121.     var $_current_token;
  122.  
  123.     /**
  124.     * The formula to parse
  125.     * @var string 
  126.     */
  127.     var $_formula;
  128.  
  129.     /**
  130.     * The character ahead of the current char
  131.     * @var string 
  132.     */
  133.     var $_lookahead;
  134.  
  135.     /**
  136.     * The parse tree to be generated
  137.     * @var string 
  138.     */
  139.     var $_parse_tree;
  140.  
  141.     /**
  142.     * The byte order. 1 => big endian, 0 => little endian.
  143.     * @var integer 
  144.     */
  145.     var $_byte_order;
  146.  
  147.     /**
  148.     * Array of external sheets
  149.     * @var array 
  150.     */
  151.     var $_ext_sheets;
  152.  
  153.     /**
  154.     * Array of sheet references in the form of REF structures
  155.     * @var array 
  156.     */
  157.     var $_references;
  158.  
  159.     /**
  160.     * The BIFF version for the workbook
  161.     * @var integer 
  162.     */
  163.     var $_BIFF_version;
  164.  
  165.     /**
  166.     * The class constructor
  167.     *
  168.     * @param integer $byte_order The byte order (Little endian or Big endian) of the architecture
  169.                                  (optional). 1 => big endian, 0 (default) little endian.
  170.     */
  171.     function Spreadsheet_Excel_Writer_Parser($byte_order$biff_version)
  172.     {
  173.         $this->_current_char  = 0;
  174.         $this->_BIFF_version  = $biff_version;
  175.         $this->_current_token = '';       // The token we are working on.
  176.         $this->_formula       = '';       // The formula to parse.
  177.         $this->_lookahead     = '';       // The character ahead of the current char.
  178.         $this->_parse_tree    = '';       // The parse tree to be generated.
  179.         $this->_initializeHashes();      // Initialize the hashes: ptg's and function's ptg's
  180.         $this->_byte_order = $byte_order// Little Endian or Big Endian
  181.         $this->_ext_sheets = array();
  182.         $this->_references = array();
  183.     }
  184.  
  185.     /**
  186.     * Initialize the ptg and function hashes.
  187.     *
  188.     * @access private
  189.     */
  190.     function _initializeHashes()
  191.     {
  192.         // The Excel ptg indices
  193.         $this->ptg array(
  194.             'ptgExp'       => 0x01,
  195.             'ptgTbl'       => 0x02,
  196.             'ptgAdd'       => 0x03,
  197.             'ptgSub'       => 0x04,
  198.             'ptgMul'       => 0x05,
  199.             'ptgDiv'       => 0x06,
  200.             'ptgPower'     => 0x07,
  201.             'ptgConcat'    => 0x08,
  202.             'ptgLT'        => 0x09,
  203.             'ptgLE'        => 0x0A,
  204.             'ptgEQ'        => 0x0B,
  205.             'ptgGE'        => 0x0C,
  206.             'ptgGT'        => 0x0D,
  207.             'ptgNE'        => 0x0E,
  208.             'ptgIsect'     => 0x0F,
  209.             'ptgUnion'     => 0x10,
  210.             'ptgRange'     => 0x11,
  211.             'ptgUplus'     => 0x12,
  212.             'ptgUminus'    => 0x13,
  213.             'ptgPercent'   => 0x14,
  214.             'ptgParen'     => 0x15,
  215.             'ptgMissArg'   => 0x16,
  216.             'ptgStr'       => 0x17,
  217.             'ptgAttr'      => 0x19,
  218.             'ptgSheet'     => 0x1A,
  219.             'ptgEndSheet'  => 0x1B,
  220.             'ptgErr'       => 0x1C,
  221.             'ptgBool'      => 0x1D,
  222.             'ptgInt'       => 0x1E,
  223.             'ptgNum'       => 0x1F,
  224.             'ptgArray'     => 0x20,
  225.             'ptgFunc'      => 0x21,
  226.             'ptgFuncVar'   => 0x22,
  227.             'ptgName'      => 0x23,
  228.             'ptgRef'       => 0x24,
  229.             'ptgArea'      => 0x25,
  230.             'ptgMemArea'   => 0x26,
  231.             'ptgMemErr'    => 0x27,
  232.             'ptgMemNoMem'  => 0x28,
  233.             'ptgMemFunc'   => 0x29,
  234.             'ptgRefErr'    => 0x2A,
  235.             'ptgAreaErr'   => 0x2B,
  236.             'ptgRefN'      => 0x2C,
  237.             'ptgAreaN'     => 0x2D,
  238.             'ptgMemAreaN'  => 0x2E,
  239.             'ptgMemNoMemN' => 0x2F,
  240.             'ptgNameX'     => 0x39,
  241.             'ptgRef3d'     => 0x3A,
  242.             'ptgArea3d'    => 0x3B,
  243.             'ptgRefErr3d'  => 0x3C,
  244.             'ptgAreaErr3d' => 0x3D,
  245.             'ptgArrayV'    => 0x40,
  246.             'ptgFuncV'     => 0x41,
  247.             'ptgFuncVarV'  => 0x42,
  248.             'ptgNameV'     => 0x43,
  249.             'ptgRefV'      => 0x44,
  250.             'ptgAreaV'     => 0x45,
  251.             'ptgMemAreaV'  => 0x46,
  252.             'ptgMemErrV'   => 0x47,
  253.             'ptgMemNoMemV' => 0x48,
  254.             'ptgMemFuncV'  => 0x49,
  255.             'ptgRefErrV'   => 0x4A,
  256.             'ptgAreaErrV'  => 0x4B,
  257.             'ptgRefNV'     => 0x4C,
  258.             'ptgAreaNV'    => 0x4D,
  259.             'ptgMemAreaNV' => 0x4E,
  260.             'ptgMemNoMemN' => 0x4F,
  261.             'ptgFuncCEV'   => 0x58,
  262.             'ptgNameXV'    => 0x59,
  263.             'ptgRef3dV'    => 0x5A,
  264.             'ptgArea3dV'   => 0x5B,
  265.             'ptgRefErr3dV' => 0x5C,
  266.             'ptgAreaErr3d' => 0x5D,
  267.             'ptgArrayA'    => 0x60,
  268.             'ptgFuncA'     => 0x61,
  269.             'ptgFuncVarA'  => 0x62,
  270.             'ptgNameA'     => 0x63,
  271.             'ptgRefA'      => 0x64,
  272.             'ptgAreaA'     => 0x65,
  273.             'ptgMemAreaA'  => 0x66,
  274.             'ptgMemErrA'   => 0x67,
  275.             'ptgMemNoMemA' => 0x68,
  276.             'ptgMemFuncA'  => 0x69,
  277.             'ptgRefErrA'   => 0x6A,
  278.             'ptgAreaErrA'  => 0x6B,
  279.             'ptgRefNA'     => 0x6C,
  280.             'ptgAreaNA'    => 0x6D,
  281.             'ptgMemAreaNA' => 0x6E,
  282.             'ptgMemNoMemN' => 0x6F,
  283.             'ptgFuncCEA'   => 0x78,
  284.             'ptgNameXA'    => 0x79,
  285.             'ptgRef3dA'    => 0x7A,
  286.             'ptgArea3dA'   => 0x7B,
  287.             'ptgRefErr3dA' => 0x7C,
  288.             'ptgAreaErr3d' => 0x7D
  289.             );
  290.  
  291.         // Thanks to Michael Meeks and Gnumeric for the initial arg values.
  292.         //
  293.         // The following hash was generated by "function_locale.pl" in the distro.
  294.         // Refer to function_locale.pl for non-English function names.
  295.         //
  296.         // The array elements are as follow:
  297.         // ptg:   The Excel function ptg code.
  298.         // args:  The number of arguments that the function takes:
  299.         //           >=0 is a fixed number of arguments.
  300.         //           -1  is a variable  number of arguments.
  301.         // class: The reference, value or array class of the function args.
  302.         // vol:   The function is volatile.
  303.         //
  304.         $this->_functions array(
  305.               // function                  ptg  args  class  vol
  306.               'COUNT'           => array(   0,   -1,    0,    ),
  307.               'IF'              => array(   1,   -1,    1,    ),
  308.               'ISNA'            => array(   2,    1,    1,    ),
  309.               'ISERROR'         => array(   3,    1,    1,    ),
  310.               'SUM'             => array(   4,   -1,    0,    ),
  311.               'AVERAGE'         => array(   5,   -1,    0,    ),
  312.               'MIN'             => array(   6,   -1,    0,    ),
  313.               'MAX'             => array(   7,   -1,    0,    ),
  314.               'ROW'             => array(   8,   -1,    0,    ),
  315.               'COLUMN'          => array(   9,   -1,    0,    ),
  316.               'NA'              => array(  10,    0,    0,    ),
  317.               'NPV'             => array(  11,   -1,    1,    ),
  318.               'STDEV'           => array(  12,   -1,    0,    ),
  319.               'DOLLAR'          => array(  13,   -1,    1,    ),
  320.               'FIXED'           => array(  14,   -1,    1,    ),
  321.               'SIN'             => array(  15,    1,    1,    ),
  322.               'COS'             => array(  16,    1,    1,    ),
  323.               'TAN'             => array(  17,    1,    1,    ),
  324.               'ATAN'            => array(  18,    1,    1,    ),
  325.               'PI'              => array(  19,    0,    1,    ),
  326.               'SQRT'            => array(  20,    1,    1,    ),
  327.               'EXP'             => array(  21,    1,    1,    ),
  328.               'LN'              => array(  22,    1,    1,    ),
  329.               'LOG10'           => array(  23,    1,    1,    ),
  330.               'ABS'             => array(  24,    1,    1,    ),
  331.               'INT'             => array(  25,    1,    1,    ),
  332.               'SIGN'            => array(  26,    1,    1,    ),
  333.               'ROUND'           => array(  27,    2,    1,    ),
  334.               'LOOKUP'          => array(  28,   -1,    0,    ),
  335.               'INDEX'           => array(  29,   -1,    0,    ),
  336.               'REPT'            => array(  30,    2,    1,    ),
  337.               'MID'             => array(  31,    3,    1,    ),
  338.               'LEN'             => array(  32,    1,    1,    ),
  339.               'VALUE'           => array(  33,    1,    1,    ),
  340.               'TRUE'            => array(  34,    0,    1,    ),
  341.               'FALSE'           => array(  35,    0,    1,    ),
  342.               'AND'             => array(  36,   -1,    0,    ),
  343.               'OR'              => array(  37,   -1,    0,    ),
  344.               'NOT'             => array(  38,    1,    1,    ),
  345.               'MOD'             => array(  39,    2,    1,    ),
  346.               'DCOUNT'          => array(  40,    3,    0,    ),
  347.               'DSUM'            => array(  41,    3,    0,    ),
  348.               'DAVERAGE'        => array(  42,    3,    0,    ),
  349.               'DMIN'            => array(  43,    3,    0,    ),
  350.               'DMAX'            => array(  44,    3,    0,    ),
  351.               'DSTDEV'          => array(  45,    3,    0,    ),
  352.               'VAR'             => array(  46,   -1,    0,    ),
  353.               'DVAR'            => array(  47,    3,    0,    ),
  354.               'TEXT'            => array(  48,    2,    1,    ),
  355.               'LINEST'          => array(  49,   -1,    0,    ),
  356.               'TREND'           => array(  50,   -1,    0,    ),
  357.               'LOGEST'          => array(  51,   -1,    0,    ),
  358.               'GROWTH'          => array(  52,   -1,    0,    ),
  359.               'PV'              => array(  56,   -1,    1,    ),
  360.               'FV'              => array(  57,   -1,    1,    ),
  361.               'NPER'            => array(  58,   -1,    1,    ),
  362.               'PMT'             => array(  59,   -1,    1,    ),
  363.               'RATE'            => array(  60,   -1,    1,    ),
  364.               'MIRR'            => array(  61,    3,    0,    ),
  365.               'IRR'             => array(  62,   -1,    0,    ),
  366.               'RAND'            => array(  63,    0,    1,    ),
  367.               'MATCH'           => array(  64,   -1,    0,    ),
  368.               'DATE'            => array(  65,    3,    1,    ),
  369.               'TIME'            => array(  66,    3,    1,    ),
  370.               'DAY'             => array(  67,    1,    1,    ),
  371.               'MONTH'           => array(  68,    1,    1,    ),
  372.               'YEAR'            => array(  69,    1,    1,    ),
  373.               'WEEKDAY'         => array(  70,   -1,    1,    ),
  374.               'HOUR'            => array(  71,    1,    1,    ),
  375.               'MINUTE'          => array(  72,    1,    1,    ),
  376.               'SECOND'          => array(  73,    1,    1,    ),
  377.               'NOW'             => array(  74,    0,    1,    ),
  378.               'AREAS'           => array(  75,    1,    0,    ),
  379.               'ROWS'            => array(  76,    1,    0,    ),
  380.               'COLUMNS'         => array(  77,    1,    0,    ),
  381.               'OFFSET'          => array(  78,   -1,    0,    ),
  382.               'SEARCH'          => array(  82,   -1,    1,    ),
  383.               'TRANSPOSE'       => array(  83,    1,    1,    ),
  384.               'TYPE'            => array(  86,    1,    1,    ),
  385.               'ATAN2'           => array(  97,    2,    1,    ),
  386.               'ASIN'            => array(  98,    1,    1,    ),
  387.               'ACOS'            => array(  99,    1,    1,    ),
  388.               'CHOOSE'          => array100,   -1,    1,    ),
  389.               'HLOOKUP'         => array101,   -1,    0,    ),
  390.               'VLOOKUP'         => array102,   -1,    0,    ),
  391.               'ISREF'           => array105,    1,    0,    ),
  392.               'LOG'             => array109,   -1,    1,    ),
  393.               'CHAR'            => array111,    1,    1,    ),
  394.               'LOWER'           => array112,    1,    1,    ),
  395.               'UPPER'           => array113,    1,    1,    ),
  396.               'PROPER'          => array114,    1,    1,    ),
  397.               'LEFT'            => array115,   -1,    1,    ),
  398.               'RIGHT'           => array116,   -1,    1,    ),
  399.               'EXACT'           => array117,    2,    1,    ),
  400.               'TRIM'            => array118,    1,    1,    ),
  401.               'REPLACE'         => array119,    4,    1,    ),
  402.               'SUBSTITUTE'      => array120,   -1,    1,    ),
  403.               'CODE'            => array121,    1,    1,    ),
  404.               'FIND'            => array124,   -1,    1,    ),
  405.               'CELL'            => array125,   -1,    0,    ),
  406.               'ISERR'           => array126,    1,    1,    ),
  407.               'ISTEXT'          => array127,    1,    1,    ),
  408.               'ISNUMBER'        => array128,    1,    1,    ),
  409.               'ISBLANK'         => array129,    1,    1,    ),
  410.               'T'               => array130,    1,    0,    ),
  411.               'N'               => array131,    1,    0,    ),
  412.               'DATEVALUE'       => array140,    1,    1,    ),
  413.               'TIMEVALUE'       => array141,    1,    1,    ),
  414.               'SLN'             => array142,    3,    1,    ),
  415.               'SYD'             => array143,    4,    1,    ),
  416.               'DDB'             => array144,   -1,    1,    ),
  417.               'INDIRECT'        => array148,   -1,    1,    ),
  418.               'CALL'            => array150,   -1,    1,    ),
  419.               'CLEAN'           => array162,    1,    1,    ),
  420.               'MDETERM'         => array163,    1,    2,    ),
  421.               'MINVERSE'        => array164,    1,    2,    ),
  422.               'MMULT'           => array165,    2,    2,    ),
  423.               'IPMT'            => array167,   -1,    1,    ),
  424.               'PPMT'            => array168,   -1,    1,    ),
  425.               'COUNTA'          => array169,   -1,    0,    ),
  426.               'PRODUCT'         => array183,   -1,    0,    ),
  427.               'FACT'            => array184,    1,    1,    ),
  428.               'DPRODUCT'        => array189,    3,    0,    ),
  429.               'ISNONTEXT'       => array190,    1,    1,    ),
  430.               'STDEVP'          => array193,   -1,    0,    ),
  431.               'VARP'            => array194,   -1,    0,    ),
  432.               'DSTDEVP'         => array195,    3,    0,    ),
  433.               'DVARP'           => array196,    3,    0,    ),
  434.               'TRUNC'           => array197,   -1,    1,    ),
  435.               'ISLOGICAL'       => array198,    1,    1,    ),
  436.               'DCOUNTA'         => array199,    3,    0,    ),
  437.               'ROUNDUP'         => array212,    2,    1,    ),
  438.               'ROUNDDOWN'       => array213,    2,    1,    ),
  439.               'RANK'            => array216,   -1,    0,    ),
  440.               'ADDRESS'         => array219,   -1,    1,    ),
  441.               'DAYS360'         => array220,   -1,    1,    ),
  442.               'TODAY'           => array221,    0,    1,    ),
  443.               'VDB'             => array222,   -1,    1,    ),
  444.               'MEDIAN'          => array227,   -1,    0,    ),
  445.               'SUMPRODUCT'      => array228,   -1,    2,    ),
  446.               'SINH'            => array229,    1,    1,    ),
  447.               'COSH'            => array230,    1,    1,    ),
  448.               'TANH'            => array231,    1,    1,    ),
  449.               'ASINH'           => array232,    1,    1,    ),
  450.               'ACOSH'           => array233,    1,    1,    ),
  451.               'ATANH'           => array234,    1,    1,    ),
  452.               'DGET'            => array235,    3,    0,    ),
  453.               'INFO'            => array244,    1,    1,    ),
  454.               'DB'              => array247,   -1,    1,    ),
  455.               'FREQUENCY'       => array252,    2,    0,    ),
  456.               'ERROR.TYPE'      => array261,    1,    1,    ),
  457.               'REGISTER.ID'     => array267,   -1,    1,    ),
  458.               'AVEDEV'          => array269,   -1,    0,    ),
  459.               'BETADIST'        => array270,   -1,    1,    ),
  460.               'GAMMALN'         => array271,    1,    1,    ),
  461.               'BETAINV'         => array272,   -1,    1,    ),
  462.               'BINOMDIST'       => array273,    4,    1,    ),
  463.               'CHIDIST'         => array274,    2,    1,    ),
  464.               'CHIINV'          => array275,    2,    1,    ),
  465.               'COMBIN'          => array276,    2,    1,    ),
  466.               'CONFIDENCE'      => array277,    3,    1,    ),
  467.               'CRITBINOM'       => array278,    3,    1,    ),
  468.               'EVEN'            => array279,    1,    1,    ),
  469.               'EXPONDIST'       => array280,    3,    1,    ),
  470.               'FDIST'           => array281,    3,    1,    ),
  471.               'FINV'            => array282,    3,    1,    ),
  472.               'FISHER'          => array283,    1,    1,    ),
  473.               'FISHERINV'       => array284,    1,    1,    ),
  474.               'FLOOR'           => array285,    2,    1,    ),
  475.               'GAMMADIST'       => array286,    4,    1,    ),
  476.               'GAMMAINV'        => array287,    3,    1,    ),
  477.               'CEILING'         => array288,    2,    1,    ),
  478.               'HYPGEOMDIST'     => array289,    4,    1,    ),
  479.               'LOGNORMDIST'     => array290,    3,    1,    ),
  480.               'LOGINV'          => array291,    3,    1,    ),
  481.               'NEGBINOMDIST'    => array292,    3,    1,    ),
  482.               'NORMDIST'        => array293,    4,    1,    ),
  483.               'NORMSDIST'       => array294,    1,    1,    ),
  484.               'NORMINV'         => array295,    3,    1,    ),
  485.               'NORMSINV'        => array296,    1,    1,    ),
  486.               'STANDARDIZE'     => array297,    3,    1,    ),
  487.               'ODD'             => array298,    1,    1,    ),
  488.               'PERMUT'          => array299,    2,    1,    ),
  489.               'POISSON'         => array300,    3,    1,    ),
  490.               'TDIST'           => array301,    3,    1,    ),
  491.               'WEIBULL'         => array302,    4,    1,    ),
  492.               'SUMXMY2'         => array303,    2,    2,    ),
  493.               'SUMX2MY2'        => array304,    2,    2,    ),
  494.               'SUMX2PY2'        => array305,    2,    2,    ),
  495.               'CHITEST'         => array306,    2,    2,    ),
  496.               'CORREL'          => array307,    2,    2,    ),
  497.               'COVAR'           => array308,    2,    2,    ),
  498.               'FORECAST'        => array309,    3,    2,    ),
  499.               'FTEST'           => array310,    2,    2,    ),
  500.               'INTERCEPT'       => array311,    2,    2,    ),
  501.               'PEARSON'         => array312,    2,    2,    ),
  502.               'RSQ'             => array313,    2,    2,    ),
  503.               'STEYX'           => array314,    2,    2,    ),
  504.               'SLOPE'           => array315,    2,    2,    ),
  505.               'TTEST'           => array316,    4,    2,    ),
  506.               'PROB'            => array317,   -1,    2,    ),
  507.               'DEVSQ'           => array318,   -1,    0,    ),
  508.               'GEOMEAN'         => array319,   -1,    0,    ),
  509.               'HARMEAN'         => array320,   -1,    0,    ),
  510.               'SUMSQ'           => array321,   -1,    0,    ),
  511.               'KURT'            => array322,   -1,    0,    ),
  512.               'SKEW'            => array323,   -1,    0,    ),
  513.               'ZTEST'           => array324,   -1,    0,    ),
  514.               'LARGE'           => array325,    2,    0,    ),
  515.               'SMALL'           => array326,    2,    0,    ),
  516.               'QUARTILE'        => array327,    2,    0,    ),
  517.               'PERCENTILE'      => array328,    2,    0,    ),
  518.               'PERCENTRANK'     => array329,   -1,    0,    ),
  519.               'MODE'            => array330,   -1,    2,    ),
  520.               'TRIMMEAN'        => array331,    2,    0,    ),
  521.               'TINV'            => array332,    2,    1,    ),
  522.               'CONCATENATE'     => array336,   -1,    1,    ),
  523.               'POWER'           => array337,    2,    1,    ),
  524.               'RADIANS'         => array342,    1,    1,    ),
  525.               'DEGREES'         => array343,    1,    1,    ),
  526.               'SUBTOTAL'        => array344,   -1,    0,    ),
  527.               'SUMIF'           => array345,   -1,    0,    ),
  528.               'COUNTIF'         => array346,    2,    0,    ),
  529.               'COUNTBLANK'      => array347,    1,    0,    ),
  530.               'ROMAN'           => array354,   -1,    1,    )
  531.               );
  532.     }
  533.  
  534.     /**
  535.     * Convert a token to the proper ptg value.
  536.     *
  537.     * @access private
  538.     * @param mixed $token The token to convert.
  539.     * @return mixed the converted token on success. PEAR_Error if the token
  540.     *                is not recognized
  541.     */
  542.     function _convert($token)
  543.     {
  544.         if (preg_match("/^\"[^\"]{0,255}\"$/"$token)) {
  545.             return $this->_convertString($token);
  546.  
  547.         elseif (is_numeric($token)) {
  548.             return $this->_convertNumber($token);
  549.  
  550.         // match references like A1 or $A$1
  551.         elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/',$token)) {
  552.             return $this->_convertRef2d($token);
  553.  
  554.         // match external references like Sheet1!A1 or Sheet1:Sheet2!A1
  555.         elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z](\d+)$/u",$token)) {
  556.             return $this->_convertRef3d($token);
  557.  
  558.         // match external references like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1
  559.         elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z](\d+)$/u",$token)) {
  560.             return $this->_convertRef3d($token);
  561.  
  562.         // match ranges like A1:B2
  563.         elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token)) {
  564.             return $this->_convertRange2d($token);
  565.  
  566.         // match ranges like A1..B2
  567.         elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token)) {
  568.             return $this->_convertRange2d($token);
  569.  
  570.         // match external ranges like Sheet1!A1 or Sheet1:Sheet2!A1:B2
  571.         elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/u",$token)) {
  572.             return $this->_convertRange3d($token);
  573.  
  574.         // match external ranges like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
  575.         elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/u",$token)) {
  576.             return $this->_convertRange3d($token);
  577.  
  578.         // operators (including parentheses)
  579.         elseif (isset($this->ptg[$token])) {
  580.             return pack("C"$this->ptg[$token]);
  581.  
  582.         // commented so argument number can be processed correctly. See toReversePolish().
  583.         /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/",$token))
  584.         {
  585.             return($this->_convertFunction($token,$this->_func_args));
  586.         }*/
  587.  
  588.         // if it's an argument, ignore the token (the argument remains)
  589.         elseif ($token == 'arg'{
  590.             return '';
  591.         }
  592.         // TODO: use real error codes
  593.         return $this->raiseError("Unknown token $token");
  594.     }
  595.  
  596.     /**
  597.     * Convert a number token to ptgInt or ptgNum
  598.     *
  599.     * @access private
  600.     * @param mixed $num an integer or double for conversion to its ptg value
  601.     */
  602.     function _convertNumber($num)
  603.     {
  604.         // Integer in the range 0..2**16-1
  605.         if ((preg_match("/^\d+$/"$num)) and ($num <= 65535)) {
  606.             return pack("Cv"$this->ptg['ptgInt']$num);
  607.         else // A float
  608.             if ($this->_byte_order// if it's Big Endian
  609.                 $num strrev($num);
  610.             }
  611.             return pack("Cd"$this->ptg['ptgNum']$num);
  612.         }
  613.     }
  614.  
  615.     /**
  616.     * Convert a string token to ptgStr
  617.     *
  618.     * @access private
  619.     * @param string $string A string for conversion to its ptg value.
  620.     * @return mixed the converted token on success. PEAR_Error if the string
  621.     *                is longer than 255 characters.
  622.     */
  623.     function _convertString($string)
  624.     {
  625.         // chop away beggining and ending quotes
  626.         $string substr($string1strlen($string2);
  627.         if (strlen($string255{
  628.             return $this->raiseError("String is too long");
  629.         }
  630.  
  631.         if ($this->_BIFF_version == 0x0500{
  632.             return pack("CC"$this->ptg['ptgStr']strlen($string)).$string;
  633.         elseif ($this->_BIFF_version == 0x0600{
  634.             $encoding 0;   // TODO: Unicode support
  635.             return pack("CCC"$this->ptg['ptgStr']strlen($string)$encoding).$string;
  636.         }
  637.     }
  638.  
  639.     /**
  640.     * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
  641.     * args that it takes.
  642.     *
  643.     * @access private
  644.     * @param string  $token    The name of the function for convertion to ptg value.
  645.     * @param integer $num_args The number of arguments the function receives.
  646.     * @return string The packed ptg for the function
  647.     */
  648.     function _convertFunction($token$num_args)
  649.     {
  650.         $args     $this->_functions[$token][1];
  651.         $volatile $this->_functions[$token][3];
  652.  
  653.         // Fixed number of args eg. TIME($i,$j,$k).
  654.         if ($args >= 0{
  655.             return pack("Cv"$this->ptg['ptgFuncV']$this->_functions[$token][0]);
  656.         }
  657.         // Variable number of args eg. SUM($i,$j,$k, ..).
  658.         if ($args == -1{
  659.             return pack("CCv"$this->ptg['ptgFuncVarV']$num_args$this->_functions[$token][0]);
  660.         }
  661.     }
  662.  
  663.     /**
  664.     * Convert an Excel range such as A1:D4 to a ptgRefV.
  665.     *
  666.     * @access private
  667.     * @param string $range An Excel range in the A1:A2 or A1..A2 format.
  668.     */
  669.     function _convertRange2d($range$class=0)
  670.     {
  671.  
  672.         // TODO: possible class value 0,1,2 check Formula.pm
  673.         // Split the range into 2 cell refs
  674.         if (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\:([A-Ia-i]?[A-Za-z])(\d+)$/"$range)) {
  675.             list($cell1$cell2split(':'$range);
  676.         elseif (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\.\.([A-Ia-i]?[A-Za-z])(\d+)$/"$range)) {
  677.             list($cell1$cell2split('\.\.'$range);
  678.  
  679.         else {
  680.             // TODO: use real error codes
  681.             return $this->raiseError("Unknown range separator"0PEAR_ERROR_DIE);
  682.         }
  683.  
  684.         // Convert the cell references
  685.         $cell_array1 $this->_cellToPackedRowcol($cell1);
  686.         if (PEAR::isError($cell_array1)) {
  687.             return $cell_array1;
  688.         }
  689.         list($row1$col1$cell_array1;
  690.         $cell_array2 $this->_cellToPackedRowcol($cell2);
  691.         if (PEAR::isError($cell_array2)) {
  692.             return $cell_array2;
  693.         }
  694.         list($row2$col2$cell_array2;
  695.  
  696.         // The ptg value depends on the class of the ptg.
  697.         if ($class == 0{
  698.             $ptgArea pack("C"$this->ptg['ptgArea']);
  699.         elseif ($class == 1{
  700.             $ptgArea pack("C"$this->ptg['ptgAreaV']);
  701.         elseif ($class == 2{
  702.             $ptgArea pack("C"$this->ptg['ptgAreaA']);
  703.         else {
  704.             // TODO: use real error codes
  705.             return $this->raiseError("Unknown class $class"0PEAR_ERROR_DIE);
  706.         }
  707.         return $ptgArea $row1 $row2 $col1$col2;
  708.     }
  709.  
  710.     /**
  711.     * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
  712.     * a ptgArea3d.
  713.     *
  714.     * @access private
  715.     * @param string $token An Excel range in the Sheet1!A1:A2 format.
  716.     * @return mixed The packed ptgArea3d token on success, PEAR_Error on failure.
  717.     */
  718.     function _convertRange3d($token)
  719.     {
  720.         $class 2// as far as I know, this is magick.
  721.  
  722.         // Split the ref at the ! symbol
  723.         list($ext_ref$rangesplit('!'$token);
  724.  
  725.         // Convert the external reference part (different for BIFF8)
  726.         if ($this->_BIFF_version == 0x0500{
  727.             $ext_ref $this->_packExtRef($ext_ref);
  728.             if (PEAR::isError($ext_ref)) {
  729.                 return $ext_ref;
  730.             }
  731.         elseif ($this->_BIFF_version == 0x0600{
  732.              $ext_ref $this->_getRefIndex($ext_ref);
  733.              if (PEAR::isError($ext_ref)) {
  734.                  return $ext_ref;
  735.              }
  736.         }
  737.  
  738.         // Split the range into 2 cell refs
  739.         list($cell1$cell2split(':'$range);
  740.  
  741.         // Convert the cell references
  742.         if (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/"$cell1)) {
  743.             $cell_array1 $this->_cellToPackedRowcol($cell1);
  744.             if (PEAR::isError($cell_array1)) {
  745.                 return $cell_array1;
  746.             }
  747.             list($row1$col1$cell_array1;
  748.             $cell_array2 $this->_cellToPackedRowcol($cell2);
  749.             if (PEAR::isError($cell_array2)) {
  750.                 return $cell_array2;
  751.             }
  752.             list($row2$col2$cell_array2;
  753.         else // It's a rows range (like 26:27)
  754.              $cells_array $this->_rangeToPackedRange($cell1.':'.$cell2);
  755.              if (PEAR::isError($cells_array)) {
  756.                  return $cells_array;
  757.              }
  758.              list($row1$col1$row2$col2$cells_array;
  759.         }
  760.  
  761.         // The ptg value depends on the class of the ptg.
  762.         if ($class == 0{
  763.             $ptgArea pack("C"$this->ptg['ptgArea3d']);
  764.         elseif ($class == 1{
  765.             $ptgArea pack("C"$this->ptg['ptgArea3dV']);
  766.         elseif ($class == 2{
  767.             $ptgArea pack("C"$this->ptg['ptgArea3dA']);
  768.         else {
  769.             return $this->raiseError("Unknown class $class"0PEAR_ERROR_DIE);
  770.         }
  771.  
  772.         return $ptgArea $ext_ref $row1 $row2 $col1$col2;
  773.     }
  774.  
  775.     /**
  776.     * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
  777.     *
  778.     * @access private
  779.     * @param string $cell An Excel cell reference
  780.     * @return string The cell in packed() format with the corresponding ptg
  781.     */
  782.     function _convertRef2d($cell)
  783.     {
  784.         $class 2// as far as I know, this is magick.
  785.  
  786.         // Convert the cell reference
  787.         $cell_array $this->_cellToPackedRowcol($cell);
  788.         if (PEAR::isError($cell_array)) {
  789.             return $cell_array;
  790.         }
  791.         list($row$col$cell_array;
  792.  
  793.         // The ptg value depends on the class of the ptg.
  794.         if ($class == 0{
  795.             $ptgRef pack("C"$this->ptg['ptgRef']);
  796.         elseif ($class == 1{
  797.             $ptgRef pack("C"$this->ptg['ptgRefV']);
  798.         elseif ($class == 2{
  799.             $ptgRef pack("C"$this->ptg['ptgRefA']);
  800.         else {
  801.             // TODO: use real error codes
  802.             return $this->raiseError("Unknown class $class");
  803.         }
  804.         return $ptgRef.$row.$col;
  805.     }
  806.  
  807.     /**
  808.     * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
  809.     * ptgRef3d.
  810.     *
  811.     * @access private
  812.     * @param string $cell An Excel cell reference
  813.     * @return mixed The packed ptgRef3d token on success, PEAR_Error on failure.
  814.     */
  815.     function _convertRef3d($cell)
  816.     {
  817.         $class 2// as far as I know, this is magick.
  818.  
  819.         // Split the ref at the ! symbol
  820.         list($ext_ref$cellsplit('!'$cell);
  821.  
  822.         // Convert the external reference part (different for BIFF8)
  823.         if ($this->_BIFF_version == 0x0500{
  824.             $ext_ref $this->_packExtRef($ext_ref);
  825.             if (PEAR::isError($ext_ref)) {
  826.                 return $ext_ref;
  827.             }
  828.         elseif ($this->_BIFF_version == 0x0600{
  829.             $ext_ref $this->_getRefIndex($ext_ref);
  830.             if (PEAR::isError($ext_ref)) {
  831.                 return $ext_ref;
  832.             }
  833.         }
  834.  
  835.         // Convert the cell reference part
  836.         list($row$col$this->_cellToPackedRowcol($cell);
  837.  
  838.         // The ptg value depends on the class of the ptg.
  839.         if ($class == 0{
  840.             $ptgRef pack("C"$this->ptg['ptgRef3d']);
  841.         elseif ($class == 1{
  842.             $ptgRef pack("C"$this->ptg['ptgRef3dV']);
  843.         elseif ($class == 2{
  844.             $ptgRef pack("C"$this->ptg['ptgRef3dA']);
  845.         else {
  846.             return $this->raiseError("Unknown class $class"0PEAR_ERROR_DIE);
  847.         }
  848.  
  849.         return $ptgRef $ext_ref$row $col;
  850.     }
  851.  
  852.     /**
  853.     * Convert the sheet name part of an external reference, for example "Sheet1" or
  854.     * "Sheet1:Sheet2", to a packed structure.
  855.     *
  856.     * @access private
  857.     * @param string $ext_ref The name of the external reference
  858.     * @return string The reference index in packed() format
  859.     */
  860.     function _packExtRef($ext_ref)
  861.     {
  862.         $ext_ref preg_replace("/^'/"''$ext_ref)// Remove leading  ' if any.
  863.         $ext_ref preg_replace("/'$/"''$ext_ref)// Remove trailing ' if any.
  864.  
  865.         // Check if there is a sheet range eg., Sheet1:Sheet2.
  866.         if (preg_match("/:/"$ext_ref)) {
  867.             list($sheet_name1$sheet_name2split(':'$ext_ref);
  868.  
  869.             $sheet1 $this->_getSheetIndex($sheet_name1);
  870.             if ($sheet1 == -1{
  871.                 return $this->raiseError("Unknown sheet name $sheet_name1 in formula");
  872.             }
  873.             $sheet2 $this->_getSheetIndex($sheet_name2);
  874.             if ($sheet2 == -1{
  875.                 return $this->raiseError("Unknown sheet name $sheet_name2 in formula");
  876.             }
  877.  
  878.             // Reverse max and min sheet numbers if necessary
  879.             if ($sheet1 $sheet2{
  880.                 list($sheet1$sheet2array($sheet2$sheet1);
  881.             }
  882.         else // Single sheet name only.
  883.             $sheet1 $this->_getSheetIndex($ext_ref);
  884.             if ($sheet1 == -1{
  885.                 return $this->raiseError("Unknown sheet name $ext_ref in formula");
  886.             }
  887.             $sheet2 $sheet1;
  888.         }
  889.  
  890.         // References are stored relative to 0xFFFF.
  891.         $offset = -$sheet1;
  892.  
  893.         return pack('vdvv'$offset0x00$sheet1$sheet2);
  894.     }
  895.  
  896.     /**
  897.     * Look up the REF index that corresponds to an external sheet name
  898.     * (or range). If it doesn't exist yet add it to the workbook's references
  899.     * array. It assumes all sheet names given must exist.
  900.     *
  901.     * @access private
  902.     * @param string $ext_ref The name of the external reference
  903.     * @return mixed The reference index in packed() format on success,
  904.     *                PEAR_Error on failure
  905.     */
  906.     function _getRefIndex($ext_ref)
  907.     {
  908.         $ext_ref preg_replace("/^'/"''$ext_ref)// Remove leading  ' if any.
  909.         $ext_ref preg_replace("/'$/"''$ext_ref)// Remove trailing ' if any.
  910.  
  911.         // Check if there is a sheet range eg., Sheet1:Sheet2.
  912.         if (preg_match("/:/"$ext_ref)) {
  913.             list($sheet_name1$sheet_name2split(':'$ext_ref);
  914.  
  915.             $sheet1 $this->_getSheetIndex($sheet_name1);
  916.             if ($sheet1 == -1{
  917.                 return $this->raiseError("Unknown sheet name $sheet_name1 in formula");
  918.             }
  919.             $sheet2 $this->_getSheetIndex($sheet_name2);
  920.             if ($sheet2 == -1{
  921.                 return $this->raiseError("Unknown sheet name $sheet_name2 in formula");
  922.             }
  923.  
  924.             // Reverse max and min sheet numbers if necessary
  925.             if ($sheet1 $sheet2{
  926.                 list($sheet1$sheet2array($sheet2$sheet1);
  927.             }
  928.         else // Single sheet name only.
  929.             $sheet1 $this->_getSheetIndex($ext_ref);
  930.             if ($sheet1 == -1{
  931.                 return $this->raiseError("Unknown sheet name $ext_ref in formula");
  932.             }
  933.             $sheet2 $sheet1;
  934.         }
  935.  
  936.         // assume all references belong to this document
  937.         $supbook_index 0x00;
  938.         $ref pack('vvv'$supbook_index$sheet1$sheet2);
  939.         $total_references count($this->_references);
  940.         $index = -1;
  941.         for ($i 0$i $total_references$i++{
  942.             if ($ref == $this->_references[$i]{
  943.                 $index $i;
  944.                 break;
  945.             }
  946.         }
  947.         // if REF was not found add it to references array
  948.         if ($index == -1{
  949.             $this->_references[$total_references$ref;
  950.             $index $total_references;
  951.         }
  952.  
  953.         return pack('v'$index);
  954.     }
  955.  
  956.     /**
  957.     * Look up the index that corresponds to an external sheet name. The hash of
  958.     * sheet names is updated by the addworksheet() method of the
  959.     * Spreadsheet_Excel_Writer_Workbook class.
  960.     *
  961.     * @access private
  962.     * @return integer The sheet index, -1 if the sheet was not found
  963.     */
  964.     function _getSheetIndex($sheet_name)
  965.     {
  966.         if (!isset($this->_ext_sheets[$sheet_name])) {
  967.             return -1;
  968.         else {
  969.             return $this->_ext_sheets[$sheet_name];
  970.         }
  971.     }
  972.  
  973.     /**
  974.     * This method is used to update the array of sheet names. It is
  975.     * called by the addWorksheet() method of the
  976.     * Spreadsheet_Excel_Writer_Workbook class.
  977.     *
  978.     * @access public
  979.     * @see Spreadsheet_Excel_Writer_Workbook::addWorksheet()
  980.     * @param string  $name  The name of the worksheet being added
  981.     * @param integer $index The index of the worksheet being added
  982.     */
  983.     function setExtSheet($name$index)
  984.     {
  985.         $this->_ext_sheets[$name$index;
  986.     }
  987.  
  988.     /**
  989.     * pack() row and column into the required 3 or 4 byte format.
  990.     *
  991.     * @access private
  992.     * @param string $cell The Excel cell reference to be packed
  993.     * @return array Array containing the row and column in packed() format
  994.     */
  995.     function _cellToPackedRowcol($cell)
  996.     {
  997.         $cell strtoupper($cell);
  998.         list($row$col$row_rel$col_rel$this->_cellToRowcol($cell);
  999.         if ($col >= 256{
  1000.             return $this->raiseError("Column in: $cell greater than 255");
  1001.         }
  1002.         // FIXME: change for BIFF8
  1003.         if ($row >= 16384{
  1004.             return $this->raiseError("Row in: $cell greater than 16384 ");
  1005.         }
  1006.  
  1007.         // Set the high bits to indicate if row or col are relative.
  1008.         if ($this->_BIFF_version == 0x0500{
  1009.             $row    |= $col_rel << 14;
  1010.             $row    |= $row_rel << 15;
  1011.             $col     pack('C'$col);
  1012.         elseif ($this->_BIFF_version == 0x0600{
  1013.             $col    |= $col_rel << 14;
  1014.             $col    |= $row_rel << 15;
  1015.             $col     pack('v'$col);
  1016.         }
  1017.         $row     pack('v'$row);
  1018.  
  1019.         return array($row$col);
  1020.     }
  1021.  
  1022.     /**
  1023.     * pack() row range into the required 3 or 4 byte format.
  1024.     * Just using maximum col/rows, which is probably not the correct solution
  1025.     *
  1026.     * @access private
  1027.     * @param string $range The Excel range to be packed
  1028.     * @return array Array containing (row1,col1,row2,col2) in packed() format
  1029.     */
  1030.     function _rangeToPackedRange($range)
  1031.     {
  1032.         preg_match('/(\$)?(\d+)\:(\$)?(\d+)/'$range$match);
  1033.         // return absolute rows if there is a $ in the ref
  1034.         $row1_rel empty($match[1]0;
  1035.         $row1     $match[2];
  1036.         $row2_rel empty($match[3]0;
  1037.         $row2     $match[4];
  1038.         // Convert 1-index to zero-index
  1039.         $row1--;
  1040.         $row2--;
  1041.         // Trick poor inocent Excel
  1042.         $col1 0;
  1043.         $col2 16383// FIXME: maximum possible value for Excel 5 (change this!!!)
  1044.  
  1045.         // FIXME: this changes for BIFF8
  1046.         if (($row1 >= 16384or ($row2 >= 16384)) {
  1047.             return $this->raiseError("Row in: $range greater than 16384 ");
  1048.         }
  1049.  
  1050.         // Set the high bits to indicate if rows are relative.
  1051.         if ($this->_BIFF_version == 0x0500{
  1052.             $row1    |= $row1_rel << 14// FIXME: probably a bug
  1053.             $row2    |= $row2_rel << 15;
  1054.             $col1     pack('C'$col1);
  1055.             $col2     pack('C'$col2);
  1056.         elseif ($this->_BIFF_version == 0x0600{
  1057.             $col1    |= $row1_rel << 15;
  1058.             $col2    |= $row2_rel << 15;
  1059.             $col1     pack('v'$col1);
  1060.             $col2     pack('v'$col2);
  1061.         }
  1062.         $row1     pack('v'$row1);
  1063.         $row2     pack('v'$row2);
  1064.  
  1065.         return array($row1$col1$row2$col2);
  1066.     }
  1067.  
  1068.     /**
  1069.     * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
  1070.     * indexed row and column number. Also returns two (0,1) values to indicate
  1071.     * whether the row or column are relative references.
  1072.     *
  1073.     * @access private
  1074.     * @param string $cell The Excel cell reference in A1 format.
  1075.     * @return array 
  1076.     */
  1077.     function _cellToRowcol($cell)
  1078.     {
  1079.         preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/',$cell,$match);
  1080.         // return absolute column if there is a $ in the ref
  1081.         $col_rel empty($match[1]0;
  1082.         $col_ref $match[2];
  1083.         $row_rel empty($match[3]0;
  1084.         $row     $match[4];
  1085.  
  1086.         // Convert base26 column string to a number.
  1087.         $expn   strlen($col_ref1;
  1088.         $col    0;
  1089.         $col_ref_length strlen($col_ref);
  1090.         for ($i 0$i $col_ref_length$i++{
  1091.             $col += (ord($col_ref{$i}ord('A'1pow(26$expn);
  1092.             $expn--;
  1093.         }
  1094.  
  1095.         // Convert 1-index to zero-index
  1096.         $row--;
  1097.         $col--;
  1098.  
  1099.         return array($row$col$row_rel$col_rel);
  1100.     }
  1101.  
  1102.     /**
  1103.     * Advance to the next valid token.
  1104.     *
  1105.     * @access private
  1106.     */
  1107.     function _advance()
  1108.     {
  1109.         $i $this->_current_char;
  1110.         $formula_length strlen($this->_formula);
  1111.         // eat up white spaces
  1112.         if ($i $formula_length{
  1113.             while ($this->_formula{$i== " "{
  1114.                 $i++;
  1115.             }
  1116.  
  1117.             if ($i ($formula_length 1)) {
  1118.                 $this->_lookahead = $this->_formula{$i+1};
  1119.             }
  1120.             $token '';
  1121.         }
  1122.  
  1123.         while ($i $formula_length{
  1124.             $token .= $this->_formula{$i};
  1125.             if ($i ($formula_length 1)) {
  1126.                 $this->_lookahead $this->_formula{$i+1};
  1127.             else {
  1128.                 $this->_lookahead '';
  1129.             }
  1130.  
  1131.             if ($this->_match($token!= ''{
  1132.                 //if ($i < strlen($this->_formula) - 1) {
  1133.                 //    $this->_lookahead = $this->_formula{$i+1};
  1134.                 //}
  1135.                 $this->_current_char $i 1;
  1136.                 $this->_current_token $token;
  1137.                 return 1;
  1138.             }
  1139.  
  1140.             if ($i ($formula_length 2)) {
  1141.                 $this->_lookahead $this->_formula{$i+2};
  1142.             else // if we run out of characters _lookahead becomes empty
  1143.                 $this->_lookahead '';
  1144.             }
  1145.             $i++;
  1146.         }
  1147.         //die("Lexical error ".$this->_current_char);
  1148.     }
  1149.  
  1150.     /**
  1151.     * Checks if it's a valid token.
  1152.     *
  1153.     * @access private
  1154.     * @param mixed $token The token to check.
  1155.     * @return mixed       The checked token or false on failure
  1156.     */
  1157.     function _match($token)
  1158.     {
  1159.         switch($token{
  1160.             case SPREADSHEET_EXCEL_WRITER_ADD:
  1161.                 return $token;
  1162.                 break;
  1163.             case SPREADSHEET_EXCEL_WRITER_SUB:
  1164.                 return $token;
  1165.                 break;
  1166.             case SPREADSHEET_EXCEL_WRITER_MUL:
  1167.                 return $token;
  1168.                 break;
  1169.             case SPREADSHEET_EXCEL_WRITER_DIV:
  1170.                 return $token;
  1171.                 break;
  1172.             case SPREADSHEET_EXCEL_WRITER_OPEN:
  1173.                 return $token;
  1174.                 break;
  1175.             case SPREADSHEET_EXCEL_WRITER_CLOSE:
  1176.                 return $token;
  1177.                 break;
  1178.             case SPREADSHEET_EXCEL_WRITER_COMA:
  1179.                 return $token;
  1180.                 break;
  1181.             case SPREADSHEET_EXCEL_WRITER_SEMICOLON:
  1182.                 return $token;
  1183.                 break;
  1184.             case SPREADSHEET_EXCEL_WRITER_GT:
  1185.                 if ($this->_lookahead == '='// it's a GE token
  1186.                     break;
  1187.                 }
  1188.                 return $token;
  1189.                 break;
  1190.             case SPREADSHEET_EXCEL_WRITER_LT:
  1191.                 // it's a LE or a NE token
  1192.                 if (($this->_lookahead == '='or ($this->_lookahead == '>')) {
  1193.                     break;
  1194.                 }
  1195.                 return $token;
  1196.                 break;
  1197.             case SPREADSHEET_EXCEL_WRITER_GE:
  1198.                 return $token;
  1199.                 break;
  1200.             case SPREADSHEET_EXCEL_WRITER_LE:
  1201.                 return $token;
  1202.                 break;
  1203.             case SPREADSHEET_EXCEL_WRITER_EQ:
  1204.                 return $token;
  1205.                 break;
  1206.             case SPREADSHEET_EXCEL_WRITER_NE:
  1207.                 return $token;
  1208.                 break;
  1209.             case SPREADSHEET_EXCEL_WRITER_CONCAT:
  1210.                 return $token;
  1211.                 break;
  1212.             default:
  1213.                 // if it's a reference
  1214.                 if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$tokenand
  1215.                    !preg_match("/[0-9]/",$this->_lookaheadand 
  1216.                    ($this->_lookahead != ':'and ($this->_lookahead != '.'and
  1217.                    ($this->_lookahead != '!'))
  1218.                 {
  1219.                     return $token;
  1220.                 }
  1221.                 // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
  1222.                 elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/u",$tokenand
  1223.                        !preg_match("/[0-9]/",$this->_lookaheadand
  1224.                        ($this->_lookahead != ':'and ($this->_lookahead != '.'))
  1225.                 {
  1226.                     return $token;
  1227.                 }
  1228.                 // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1)
  1229.                 elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/u",$tokenand
  1230.                        !preg_match("/[0-9]/",$this->_lookaheadand
  1231.                        ($this->_lookahead != ':'and ($this->_lookahead != '.'))
  1232.                 {
  1233.                     return $token;
  1234.                 }
  1235.                 // if it's a range (A1:A2)
  1236.                 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$tokenand 
  1237.                        !preg_match("/[0-9]/",$this->_lookahead))
  1238.                 {
  1239.                     return $token;
  1240.                 }
  1241.                 // if it's a range (A1..A2)
  1242.                 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$tokenand 
  1243.                        !preg_match("/[0-9]/",$this->_lookahead))
  1244.                 {
  1245.                     return $token;
  1246.                 }
  1247.                 // If it's an external range like Sheet1!A1 or Sheet1:Sheet2!A1:B2
  1248.                 elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$tokenand
  1249.                        !preg_match("/[0-9]/",$this->_lookahead))
  1250.                 {
  1251.                     return $token;
  1252.                 }
  1253.                 // If it's an external range like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
  1254.                 elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$tokenand
  1255.                        !preg_match("/[0-9]/",$this->_lookahead))
  1256.                 {
  1257.                     return $token;
  1258.                 }
  1259.                 // If it's a number (check that it's not a sheet name or range)
  1260.                 elseif (is_numeric($tokenand 
  1261.                         (!is_numeric($token.$this->_lookaheador ($this->_lookahead == '')) and
  1262.                         ($this->_lookahead != '!'and ($this->_lookahead != ':'))
  1263.                 {
  1264.                     return $token;
  1265.                 }
  1266.                 // If it's a string (of maximum 255 characters)
  1267.                 elseif (preg_match("/^\"[^\"]{0,255}\"$/",$token))
  1268.                 {
  1269.                     return $token;
  1270.                 }
  1271.                 // if it's a function call
  1272.                 elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i",$tokenand ($this->_lookahead == "("))
  1273.                 {
  1274.                     return $token;
  1275.                 }
  1276.                 return '';
  1277.         }
  1278.     }
  1279.  
  1280.     /**
  1281.     * The parsing method. It parses a formula.
  1282.     *
  1283.     * @access public
  1284.     * @param string $formula The formula to parse, without the initial equal
  1285.     *                         sign (=).
  1286.     * @return mixed true on success, PEAR_Error on failure
  1287.     */
  1288.     function parse($formula)
  1289.     {
  1290.         $this->_current_char 0;
  1291.         $this->_formula      $formula;
  1292.         $this->_lookahead    $formula{1};
  1293.         $this->_advance();
  1294.         $this->_parse_tree   $this->_condition();
  1295.         if (PEAR::isError($this->_parse_tree)) {
  1296.             return $this->_parse_tree;
  1297.         }
  1298.         return true;
  1299.     }
  1300.  
  1301.     /**
  1302.     * It parses a condition. It assumes the following rule:
  1303.     * Cond -> Expr [(">" | "<") Expr]
  1304.     *
  1305.     * @access private
  1306.     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
  1307.     */
  1308.     function _condition()
  1309.     {
  1310.         $result $this->_expression();
  1311.         if (PEAR::isError($result)) {
  1312.             return $result;
  1313.         }
  1314.         if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LT{
  1315.             $this->_advance();
  1316.             $result2 $this->_expression();
  1317.             if (PEAR::isError($result2)) {
  1318.                 return $result2;
  1319.             }
  1320.             $result $this->_createTree('ptgLT'$result$result2);
  1321.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GT{
  1322.             $this->_advance();
  1323.             $result2 $this->_expression();
  1324.             if (PEAR::isError($result2)) {
  1325.                 return $result2;
  1326.             }
  1327.             $result $this->_createTree('ptgGT'$result$result2);
  1328.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LE{
  1329.             $this->_advance();
  1330.             $result2 $this->_expression();
  1331.             if (PEAR::isError($result2)) {
  1332.                 return $result2;
  1333.             }
  1334.             $result $this->_createTree('ptgLE'$result$result2);
  1335.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GE{
  1336.             $this->_advance();
  1337.             $result2 $this->_expression();
  1338.             if (PEAR::isError($result2)) {
  1339.                 return $result2;
  1340.             }
  1341.             $result $this->_createTree('ptgGE'$result$result2);
  1342.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_EQ{
  1343.             $this->_advance();
  1344.             $result2 $this->_expression();
  1345.             if (PEAR::isError($result2)) {
  1346.                 return $result2;
  1347.             }
  1348.             $result $this->_createTree('ptgEQ'$result$result2);
  1349.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_NE{
  1350.             $this->_advance();
  1351.             $result2 $this->_expression();
  1352.             if (PEAR::isError($result2)) {
  1353.                 return $result2;
  1354.             }
  1355.             $result $this->_createTree('ptgNE'$result$result2);
  1356.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_CONCAT{
  1357.             $this->_advance();
  1358.             $result2 $this->_expression();
  1359.             if (PEAR::isError($result2)) {
  1360.                 return $result2;
  1361.         }
  1362.             $result $this->_createTree('ptgConcat'$result$result2);
  1363.         }
  1364.         return $result;
  1365.     }
  1366.  
  1367.     /**
  1368.     * It parses a expression. It assumes the following rule:
  1369.     * Expr -> Term [("+" | "-") Term]
  1370.     *      -> "string"
  1371.     *      -> "-" Term
  1372.     *
  1373.     * @access private
  1374.     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
  1375.     */
  1376.     function _expression()
  1377.     {
  1378.         // If it's a string return a string node
  1379.         if (preg_match("/^\"[^\"]{0,255}\"$/"$this->_current_token)) {
  1380.             $result $this->_createTree($this->_current_token'''');
  1381.             $this->_advance();
  1382.             return $result;
  1383.         elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB{
  1384.             // catch "-" Term
  1385.             $this->_advance();
  1386.             $result2 $this->_expression();
  1387.             $result $this->_createTree('ptgUminus'$result2'');
  1388.             return $result;
  1389.         }
  1390.         $result $this->_term();
  1391.         if (PEAR::isError($result)) {
  1392.             return $result;
  1393.         }
  1394.         while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADDor
  1395.                ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB)) {
  1396.         /**/
  1397.             if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD{
  1398.                 $this->_advance();
  1399.                 $result2 $this->_term();
  1400.                 if (PEAR::isError($result2)) {
  1401.                     return $result2;
  1402.                 }
  1403.                 $result $this->_createTree('ptgAdd'$result$result2);
  1404.             else {
  1405.                 $this->_advance();
  1406.                 $result2 $this->_term();
  1407.                 if (PEAR::isError($result2)) {
  1408.                     return $result2;
  1409.                 }
  1410.                 $result $this->_createTree('ptgSub'$result$result2);
  1411.             }
  1412.         }
  1413.         return $result;
  1414.     }
  1415.  
  1416.     /**
  1417.     * This function just introduces a ptgParen element in the tree, so that Excel
  1418.     * doesn't get confused when working with a parenthesized formula afterwards.
  1419.     *
  1420.     * @access private
  1421.     * @see _fact()
  1422.     * @return array The parsed ptg'd tree
  1423.     */
  1424.     function _parenthesizedExpression()
  1425.     {
  1426.         $result $this->_createTree('ptgParen'$this->_expression()'');
  1427.         return $result;
  1428.     }
  1429.  
  1430.     /**
  1431.     * It parses a term. It assumes the following rule:
  1432.     * Term -> Fact [("*" | "/") Fact]
  1433.     *
  1434.     * @access private
  1435.     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
  1436.     */
  1437.     function _term()
  1438.     {
  1439.         $result $this->_fact();
  1440.         if (PEAR::isError($result)) {
  1441.             return $result;
  1442.         }
  1443.         while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_MULor
  1444.                ($this->_current_token == SPREADSHEET_EXCEL_WRITER_DIV)) {
  1445.         /**/
  1446.             if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL{
  1447.                 $this->_advance();
  1448.                 $result2 $this->_fact();
  1449.                 if (PEAR::isError($result2)) {
  1450.                     return $result2;
  1451.                 }
  1452.                 $result $this->_createTree('ptgMul'$result$result2);
  1453.             else {
  1454.                 $this->_advance();
  1455.                 $result2 $this->_fact();
  1456.                 if (PEAR::isError($result2)) {
  1457.                     return $result2;
  1458.                 }
  1459.                 $result $this->_createTree('ptgDiv'$result$result2);
  1460.             }
  1461.         }
  1462.         return $result;
  1463.     }
  1464.  
  1465.     /**
  1466.     * It parses a factor. It assumes the following rule:
  1467.     * Fact -> ( Expr )
  1468.     *       | CellRef
  1469.     *       | CellRange
  1470.     *       | Number
  1471.     *       | Function
  1472.     *
  1473.     * @access private
  1474.     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
  1475.     */
  1476.     function _fact()
  1477.     {
  1478.         if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_OPEN{
  1479.             $this->_advance();         // eat the "("
  1480.             $result $this->_parenthesizedExpression();
  1481.             if ($this->_current_token != SPREADSHEET_EXCEL_WRITER_CLOSE{
  1482.                 return $this->raiseError("')' token expected.");
  1483.             }
  1484.             $this->_advance();         // eat the ")"
  1485.             return $result;
  1486.         }
  1487.         // if it's a reference
  1488.         if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$this->_current_token))
  1489.         {
  1490.             $result $this->_createTree($this->_current_token'''');
  1491.             $this->_advance();
  1492.             return $result;
  1493.         }
  1494.         // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
  1495.         elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/u",$this->_current_token))
  1496.         {
  1497.             $result $this->_createTree($this->_current_token'''');
  1498.             $this->_advance();
  1499.             return $result;
  1500.         }
  1501.         // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1)
  1502.         elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/u",$this->_current_token))
  1503.         {
  1504.             $result $this->_createTree($this->_current_token'''');
  1505.             $this->_advance();
  1506.             return $result;
  1507.         }
  1508.         // if it's a range
  1509.         elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_tokenor 
  1510.                 preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token))
  1511.         {
  1512.             $result $this->_current_token;
  1513.             $this->_advance();
  1514.             return $result;
  1515.         }
  1516.         // If it's an external range (Sheet1!A1 or Sheet1!A1:B2)
  1517.         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))
  1518.         {
  1519.             $result $this->_current_token;
  1520.             $this->_advance();
  1521.             return $result;
  1522.         }
  1523.         // If it's an external range ('Sheet1'!A1 or 'Sheet1'!A1:B2)
  1524.         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))
  1525.         {
  1526.             $result $this->_current_token;
  1527.             $this->_advance();
  1528.             return $result;
  1529.         }
  1530.         elseif (is_numeric($this->_current_token))
  1531.         {
  1532.             $result $this->_createTree($this->_current_token'''');
  1533.             $this->_advance();
  1534.             return $result;
  1535.         }
  1536.         // if it's a function call
  1537.         elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i",$this->_current_token))
  1538.         {
  1539.             $result $this->_func();
  1540.             return $result;
  1541.         }
  1542.         return $this->raiseError("Syntax error: ".$this->_current_token.
  1543.                                  ", lookahead: ".$this->_lookahead.
  1544.                                  ", current char: ".$this->_current_char);
  1545.     }
  1546.  
  1547.     /**
  1548.     * It parses a function call. It assumes the following rule:
  1549.     * Func -> ( Expr [,Expr]* )
  1550.     *
  1551.     * @access private
  1552.     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
  1553.     */
  1554.     function _func()
  1555.     {
  1556.         $num_args 0// number of arguments received
  1557.         $function strtoupper($this->_current_token);
  1558.         $result   ''// initialize result
  1559.         $this->_advance();
  1560.         $this->_advance();         // eat the "("
  1561.         while ($this->_current_token != ')'{
  1562.         /**/
  1563.             if ($num_args 0{
  1564.                 if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_COMA or
  1565.                     $this->_current_token == SPREADSHEET_EXCEL_WRITER_SEMICOLON)
  1566.                 {
  1567.                     $this->_advance();  // eat the "," or ";"
  1568.                 else {
  1569.                     return $this->raiseError("Syntax error: comma expected in ".
  1570.                                       "function $function, arg #{$num_args}");
  1571.                 }
  1572.                 $result2 $this->_condition();
  1573.                 if (PEAR::isError($result2)) {
  1574.                     return $result2;
  1575.                 }
  1576.                 $result $this->_createTree('arg'$result$result2);
  1577.             else // first argument
  1578.                 $result2 $this->_condition();
  1579.                 if (PEAR::isError($result2)) {
  1580.                     return $result2;
  1581.                 }
  1582.                 $result $this->_createTree('arg'''$result2);
  1583.             }
  1584.             $num_args++;
  1585.         }
  1586.         if (!isset($this->_functions[$function])) {
  1587.             return $this->raiseError("Function $function() doesn't exist");
  1588.         }
  1589.         $args $this->_functions[$function][1];
  1590.         // If fixed number of args eg. TIME($i,$j,$k). Check that the number of args is valid.
  1591.         if (($args >= 0and ($args != $num_args)) {
  1592.             return $this->raiseError("Incorrect number of arguments in function $function() ");
  1593.         }
  1594.  
  1595.         $result $this->_createTree($function$result$num_args);
  1596.         $this->_advance();         // eat the ")"
  1597.         return $result;
  1598.     }
  1599.  
  1600.     /**
  1601.     * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
  1602.     * as elements.
  1603.     *
  1604.     * @access private
  1605.     * @param mixed $value The value of this node.
  1606.     * @param mixed $left  The left array (sub-tree) or a final node.
  1607.     * @param mixed $right The right array (sub-tree) or a final node.
  1608.     * @return array A tree
  1609.     */
  1610.     function _createTree($value$left$right)
  1611.     {
  1612.         return array('value' => $value'left' => $left'right' => $right);
  1613.     }
  1614.  
  1615.     /**
  1616.     * Builds a string containing the tree in reverse polish notation (What you
  1617.     * would use in a HP calculator stack).
  1618.     * The following tree:
  1619.     *
  1620.     *    +
  1621.     *   / \
  1622.     *  2   3
  1623.     *
  1624.     * produces: "23+"
  1625.     *
  1626.     * The following tree:
  1627.     *
  1628.     *    +
  1629.     *   / \
  1630.     *  3   *
  1631.     *     / \
  1632.     *    6   A1
  1633.     *
  1634.     * produces: "36A1*+"
  1635.     *
  1636.     * In fact all operands, functions, references, etc... are written as ptg's
  1637.     *
  1638.     * @access public
  1639.     * @param array $tree The optional tree to convert.
  1640.     * @return string The tree in reverse polish notation
  1641.     */
  1642.     function toReversePolish($tree array())
  1643.     {
  1644.         $polish ""// the string we are going to return
  1645.         if (empty($tree)) // If it's the first call use _parse_tree
  1646.             $tree $this->_parse_tree;
  1647.         }
  1648.         if (is_array($tree['left'])) {
  1649.             $converted_tree $this->toReversePolish($tree['left']);
  1650.             if (PEAR::isError($converted_tree)) {
  1651.                 return $converted_tree;
  1652.             }
  1653.             $polish .= $converted_tree;
  1654.         elseif ($tree['left'!= ''// It's a final node
  1655.             $converted_tree $this->_convert($tree['left']);
  1656.             if (PEAR::isError($converted_tree)) {
  1657.                 return $converted_tree;
  1658.             }
  1659.             $polish .= $converted_tree;
  1660.         }
  1661.         if (is_array($tree['right'])) {
  1662.             $converted_tree $this->toReversePolish($tree['right']);
  1663.             if (PEAR::isError($converted_tree)) {
  1664.                 return $converted_tree;
  1665.             }
  1666.             $polish .= $converted_tree;
  1667.         elseif ($tree['right'!= ''// It's a final node
  1668.             $converted_tree $this->_convert($tree['right']);
  1669.             if (PEAR::isError($converted_tree)) {
  1670.                 return $converted_tree;
  1671.             }
  1672.             $polish .= $converted_tree;
  1673.         }
  1674.         // if it's a function convert it here (so we can set it's arguments)
  1675.         if (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/",$tree['value']and
  1676.             !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/',$tree['value']and
  1677.             !preg_match("/^[A-Ia-i]?[A-Za-z](\d+)\.\.[A-Ia-i]?[A-Za-z](\d+)$/",$tree['value']and
  1678.             !is_numeric($tree['value']and
  1679.             !isset($this->ptg[$tree['value']]))
  1680.         {
  1681.             // left subtree for a function is always an array.
  1682.             if ($tree['left'!= ''{
  1683.                 $left_tree $this->toReversePolish($tree['left']);
  1684.             else {
  1685.                 $left_tree '';
  1686.             }
  1687.             if (PEAR::isError($left_tree)) {
  1688.                 return $left_tree;
  1689.             }
  1690.             // add it's left subtree and return.
  1691.             return $left_tree.$this->_convertFunction($tree['value']$tree['right']);
  1692.         else {
  1693.             $converted_tree $this->_convert($tree['value']);
  1694.             if (PEAR::isError($converted_tree)) {
  1695.                 return $converted_tree;
  1696.             }
  1697.         }
  1698.         $polish .= $converted_tree;
  1699.         return $polish;
  1700.     }
  1701. }
  1702. ?>

Documentation generated on Wed, 09 Feb 2011 09:03:02 +0700 by phpDocumentor 1.4.2