Completed
Push — develop ( 3ee9cc...870d86 )
by Adrien
29:45
created

Parser::parse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
4
5
/**
6
 * Copyright (c) 2006 - 2015 PhpSpreadsheet.
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
 *
22
 * @category   PhpSpreadsheet
23
 *
24
 * @copyright  Copyright (c) 2006 - 2015 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
25
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
26
 */
27
28
// Original file header of PEAR::Spreadsheet_Excel_Writer_Parser (used as the base for this class):
29
// -----------------------------------------------------------------------------------------
30
// *  Class for parsing Excel formulas
31
// *
32
// *  License Information:
33
// *
34
// *    Spreadsheet_Excel_Writer:  A library for generating Excel Spreadsheets
35
// *    Copyright (c) 2002-2003 Xavier Noguer [email protected]
36
// *
37
// *    This library is free software; you can redistribute it and/or
38
// *    modify it under the terms of the GNU Lesser General Public
39
// *    License as published by the Free Software Foundation; either
40
// *    version 2.1 of the License, or (at your option) any later version.
41
// *
42
// *    This library is distributed in the hope that it will be useful,
43
// *    but WITHOUT ANY WARRANTY; without even the implied warranty of
44
// *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45
// *    Lesser General Public License for more details.
46
// *
47
// *    You should have received a copy of the GNU Lesser General Public
48
// *    License along with this library; if not, write to the Free Software
49
// *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
50
// */
51
class Parser
52
{
53
    /**    Constants                */
54
    // Sheet title in unquoted form
55
    // Invalid sheet title characters cannot occur in the sheet title:
56
    //         *:/\?[]
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
57
    // Moreover, there are valid sheet title characters that cannot occur in unquoted form (there may be more?)
58
    // +-% '^&<>=,;#()"{}
59
    const REGEX_SHEET_TITLE_UNQUOTED = '[^\*\:\/\\\\\?\[\]\+\-\% \\\'\^\&\<\>\=\,\;\#\(\)\"\{\}]+';
60
61
    // Sheet title in quoted form (without surrounding quotes)
62
    // Invalid sheet title characters cannot occur in the sheet title:
63
    // *:/\?[]                    (usual invalid sheet title characters)
64
    // Single quote is represented as a pair ''
65
    const REGEX_SHEET_TITLE_QUOTED = '(([^\*\:\/\\\\\?\[\]\\\'])+|(\\\'\\\')+)+';
66
67
    /**
68
     * The index of the character we are currently looking at.
69
     *
70
     * @var int
71
     */
72
    public $currentCharacter;
73
74
    /**
75
     * The token we are working on.
76
     *
77
     * @var string
78
     */
79
    public $currentToken;
80
81
    /**
82
     * The formula to parse.
83
     *
84
     * @var string
85
     */
86
    private $formula;
87
88
    /**
89
     * The character ahead of the current char.
90
     *
91
     * @var string
92
     */
93
    public $lookAhead;
94
95
    /**
96
     * The parse tree to be generated.
97
     *
98
     * @var string
99
     */
100
    private $parseTree;
101
102
    /**
103
     * Array of external sheets.
104
     *
105
     * @var array
106
     */
107
    private $externalSheets;
108
109
    /**
110
     * Array of sheet references in the form of REF structures.
111
     *
112
     * @var array
113
     */
114
    public $references;
115
116
    /**
117
     * The class constructor.
118
     */
119 37
    public function __construct()
120
    {
121 37
        $this->currentCharacter = 0;
122 37
        $this->currentToken = ''; // The token we are working on.
123 37
        $this->formula = ''; // The formula to parse.
124 37
        $this->lookAhead = ''; // The character ahead of the current char.
125 37
        $this->parseTree = ''; // The parse tree to be generated.
126 37
        $this->initializeHashes(); // Initialize the hashes: ptg's and function's ptg's
127 37
        $this->externalSheets = [];
128 37
        $this->references = [];
129 37
    }
130
131
    /**
132
     * Initialize the ptg and function hashes.
133
     */
134 37
    private function initializeHashes()
135
    {
136
        // The Excel ptg indices
137 37
        $this->ptg = [
0 ignored issues
show
Bug introduced by
The property ptg does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
138
            'ptgExp' => 0x01,
139
            'ptgTbl' => 0x02,
140
            'ptgAdd' => 0x03,
141
            'ptgSub' => 0x04,
142
            'ptgMul' => 0x05,
143
            'ptgDiv' => 0x06,
144
            'ptgPower' => 0x07,
145
            'ptgConcat' => 0x08,
146
            'ptgLT' => 0x09,
147
            'ptgLE' => 0x0A,
148
            'ptgEQ' => 0x0B,
149
            'ptgGE' => 0x0C,
150
            'ptgGT' => 0x0D,
151
            'ptgNE' => 0x0E,
152
            'ptgIsect' => 0x0F,
153
            'ptgUnion' => 0x10,
154
            'ptgRange' => 0x11,
155
            'ptgUplus' => 0x12,
156
            'ptgUminus' => 0x13,
157
            'ptgPercent' => 0x14,
158
            'ptgParen' => 0x15,
159
            'ptgMissArg' => 0x16,
160
            'ptgStr' => 0x17,
161
            'ptgAttr' => 0x19,
162
            'ptgSheet' => 0x1A,
163
            'ptgEndSheet' => 0x1B,
164
            'ptgErr' => 0x1C,
165
            'ptgBool' => 0x1D,
166
            'ptgInt' => 0x1E,
167
            'ptgNum' => 0x1F,
168
            'ptgArray' => 0x20,
169
            'ptgFunc' => 0x21,
170
            'ptgFuncVar' => 0x22,
171
            'ptgName' => 0x23,
172
            'ptgRef' => 0x24,
173
            'ptgArea' => 0x25,
174
            'ptgMemArea' => 0x26,
175
            'ptgMemErr' => 0x27,
176
            'ptgMemNoMem' => 0x28,
177
            'ptgMemFunc' => 0x29,
178
            'ptgRefErr' => 0x2A,
179
            'ptgAreaErr' => 0x2B,
180
            'ptgRefN' => 0x2C,
181
            'ptgAreaN' => 0x2D,
182
            'ptgMemAreaN' => 0x2E,
183
            'ptgMemNoMemN' => 0x2F,
184
            'ptgNameX' => 0x39,
185
            'ptgRef3d' => 0x3A,
186
            'ptgArea3d' => 0x3B,
187
            'ptgRefErr3d' => 0x3C,
188
            'ptgAreaErr3d' => 0x3D,
189
            'ptgArrayV' => 0x40,
190
            'ptgFuncV' => 0x41,
191
            'ptgFuncVarV' => 0x42,
192
            'ptgNameV' => 0x43,
193
            'ptgRefV' => 0x44,
194
            'ptgAreaV' => 0x45,
195
            'ptgMemAreaV' => 0x46,
196
            'ptgMemErrV' => 0x47,
197
            'ptgMemNoMemV' => 0x48,
198
            'ptgMemFuncV' => 0x49,
199
            'ptgRefErrV' => 0x4A,
200
            'ptgAreaErrV' => 0x4B,
201
            'ptgRefNV' => 0x4C,
202
            'ptgAreaNV' => 0x4D,
203
            'ptgMemAreaNV' => 0x4E,
204
            'ptgMemNoMemNV' => 0x4F,
205
            'ptgFuncCEV' => 0x58,
206
            'ptgNameXV' => 0x59,
207
            'ptgRef3dV' => 0x5A,
208
            'ptgArea3dV' => 0x5B,
209
            'ptgRefErr3dV' => 0x5C,
210
            'ptgAreaErr3dV' => 0x5D,
211
            'ptgArrayA' => 0x60,
212
            'ptgFuncA' => 0x61,
213
            'ptgFuncVarA' => 0x62,
214
            'ptgNameA' => 0x63,
215
            'ptgRefA' => 0x64,
216
            'ptgAreaA' => 0x65,
217
            'ptgMemAreaA' => 0x66,
218
            'ptgMemErrA' => 0x67,
219
            'ptgMemNoMemA' => 0x68,
220
            'ptgMemFuncA' => 0x69,
221
            'ptgRefErrA' => 0x6A,
222
            'ptgAreaErrA' => 0x6B,
223
            'ptgRefNA' => 0x6C,
224
            'ptgAreaNA' => 0x6D,
225
            'ptgMemAreaNA' => 0x6E,
226
            'ptgMemNoMemNA' => 0x6F,
227
            'ptgFuncCEA' => 0x78,
228
            'ptgNameXA' => 0x79,
229
            'ptgRef3dA' => 0x7A,
230
            'ptgArea3dA' => 0x7B,
231
            'ptgRefErr3dA' => 0x7C,
232
            'ptgAreaErr3dA' => 0x7D,
233
        ];
234
235
        // Thanks to Michael Meeks and Gnumeric for the initial arg values.
236
        //
237
        // The following hash was generated by "function_locale.pl" in the distro.
238
        // Refer to function_locale.pl for non-English function names.
239
        //
240
        // The array elements are as follow:
241
        // ptg:   The Excel function ptg code.
242
        // args:  The number of arguments that the function takes:
243
        //           >=0 is a fixed number of arguments.
244
        //           -1  is a variable  number of arguments.
245
        // class: The reference, value or array class of the function args.
246
        // vol:   The function is volatile.
247
        //
248 37
        $this->functions = [
0 ignored issues
show
Bug introduced by
The property functions does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
249
            // function                  ptg  args  class  vol
250
            'COUNT' => [0, -1, 0, 0],
251
            'IF' => [1, -1, 1, 0],
252
            'ISNA' => [2, 1, 1, 0],
253
            'ISERROR' => [3, 1, 1, 0],
254
            'SUM' => [4, -1, 0, 0],
255
            'AVERAGE' => [5, -1, 0, 0],
256
            'MIN' => [6, -1, 0, 0],
257
            'MAX' => [7, -1, 0, 0],
258
            'ROW' => [8, -1, 0, 0],
259
            'COLUMN' => [9, -1, 0, 0],
260
            'NA' => [10, 0, 0, 0],
261
            'NPV' => [11, -1, 1, 0],
262
            'STDEV' => [12, -1, 0, 0],
263
            'DOLLAR' => [13, -1, 1, 0],
264
            'FIXED' => [14, -1, 1, 0],
265
            'SIN' => [15, 1, 1, 0],
266
            'COS' => [16, 1, 1, 0],
267
            'TAN' => [17, 1, 1, 0],
268
            'ATAN' => [18, 1, 1, 0],
269
            'PI' => [19, 0, 1, 0],
270
            'SQRT' => [20, 1, 1, 0],
271
            'EXP' => [21, 1, 1, 0],
272
            'LN' => [22, 1, 1, 0],
273
            'LOG10' => [23, 1, 1, 0],
274
            'ABS' => [24, 1, 1, 0],
275
            'INT' => [25, 1, 1, 0],
276
            'SIGN' => [26, 1, 1, 0],
277
            'ROUND' => [27, 2, 1, 0],
278
            'LOOKUP' => [28, -1, 0, 0],
279
            'INDEX' => [29, -1, 0, 1],
280
            'REPT' => [30, 2, 1, 0],
281
            'MID' => [31, 3, 1, 0],
282
            'LEN' => [32, 1, 1, 0],
283
            'VALUE' => [33, 1, 1, 0],
284
            'TRUE' => [34, 0, 1, 0],
285
            'FALSE' => [35, 0, 1, 0],
286
            'AND' => [36, -1, 0, 0],
287
            'OR' => [37, -1, 0, 0],
288
            'NOT' => [38, 1, 1, 0],
289
            'MOD' => [39, 2, 1, 0],
290
            'DCOUNT' => [40, 3, 0, 0],
291
            'DSUM' => [41, 3, 0, 0],
292
            'DAVERAGE' => [42, 3, 0, 0],
293
            'DMIN' => [43, 3, 0, 0],
294
            'DMAX' => [44, 3, 0, 0],
295
            'DSTDEV' => [45, 3, 0, 0],
296
            'VAR' => [46, -1, 0, 0],
297
            'DVAR' => [47, 3, 0, 0],
298
            'TEXT' => [48, 2, 1, 0],
299
            'LINEST' => [49, -1, 0, 0],
300
            'TREND' => [50, -1, 0, 0],
301
            'LOGEST' => [51, -1, 0, 0],
302
            'GROWTH' => [52, -1, 0, 0],
303
            'PV' => [56, -1, 1, 0],
304
            'FV' => [57, -1, 1, 0],
305
            'NPER' => [58, -1, 1, 0],
306
            'PMT' => [59, -1, 1, 0],
307
            'RATE' => [60, -1, 1, 0],
308
            'MIRR' => [61, 3, 0, 0],
309
            'IRR' => [62, -1, 0, 0],
310
            'RAND' => [63, 0, 1, 1],
311
            'MATCH' => [64, -1, 0, 0],
312
            'DATE' => [65, 3, 1, 0],
313
            'TIME' => [66, 3, 1, 0],
314
            'DAY' => [67, 1, 1, 0],
315
            'MONTH' => [68, 1, 1, 0],
316
            'YEAR' => [69, 1, 1, 0],
317
            'WEEKDAY' => [70, -1, 1, 0],
318
            'HOUR' => [71, 1, 1, 0],
319
            'MINUTE' => [72, 1, 1, 0],
320
            'SECOND' => [73, 1, 1, 0],
321
            'NOW' => [74, 0, 1, 1],
322
            'AREAS' => [75, 1, 0, 1],
323
            'ROWS' => [76, 1, 0, 1],
324
            'COLUMNS' => [77, 1, 0, 1],
325
            'OFFSET' => [78, -1, 0, 1],
326
            'SEARCH' => [82, -1, 1, 0],
327
            'TRANSPOSE' => [83, 1, 1, 0],
328
            'TYPE' => [86, 1, 1, 0],
329
            'ATAN2' => [97, 2, 1, 0],
330
            'ASIN' => [98, 1, 1, 0],
331
            'ACOS' => [99, 1, 1, 0],
332
            'CHOOSE' => [100, -1, 1, 0],
333
            'HLOOKUP' => [101, -1, 0, 0],
334
            'VLOOKUP' => [102, -1, 0, 0],
335
            'ISREF' => [105, 1, 0, 0],
336
            'LOG' => [109, -1, 1, 0],
337
            'CHAR' => [111, 1, 1, 0],
338
            'LOWER' => [112, 1, 1, 0],
339
            'UPPER' => [113, 1, 1, 0],
340
            'PROPER' => [114, 1, 1, 0],
341
            'LEFT' => [115, -1, 1, 0],
342
            'RIGHT' => [116, -1, 1, 0],
343
            'EXACT' => [117, 2, 1, 0],
344
            'TRIM' => [118, 1, 1, 0],
345
            'REPLACE' => [119, 4, 1, 0],
346
            'SUBSTITUTE' => [120, -1, 1, 0],
347
            'CODE' => [121, 1, 1, 0],
348
            'FIND' => [124, -1, 1, 0],
349
            'CELL' => [125, -1, 0, 1],
350
            'ISERR' => [126, 1, 1, 0],
351
            'ISTEXT' => [127, 1, 1, 0],
352
            'ISNUMBER' => [128, 1, 1, 0],
353
            'ISBLANK' => [129, 1, 1, 0],
354
            'T' => [130, 1, 0, 0],
355
            'N' => [131, 1, 0, 0],
356
            'DATEVALUE' => [140, 1, 1, 0],
357
            'TIMEVALUE' => [141, 1, 1, 0],
358
            'SLN' => [142, 3, 1, 0],
359
            'SYD' => [143, 4, 1, 0],
360
            'DDB' => [144, -1, 1, 0],
361
            'INDIRECT' => [148, -1, 1, 1],
362
            'CALL' => [150, -1, 1, 0],
363
            'CLEAN' => [162, 1, 1, 0],
364
            'MDETERM' => [163, 1, 2, 0],
365
            'MINVERSE' => [164, 1, 2, 0],
366
            'MMULT' => [165, 2, 2, 0],
367
            'IPMT' => [167, -1, 1, 0],
368
            'PPMT' => [168, -1, 1, 0],
369
            'COUNTA' => [169, -1, 0, 0],
370
            'PRODUCT' => [183, -1, 0, 0],
371
            'FACT' => [184, 1, 1, 0],
372
            'DPRODUCT' => [189, 3, 0, 0],
373
            'ISNONTEXT' => [190, 1, 1, 0],
374
            'STDEVP' => [193, -1, 0, 0],
375
            'VARP' => [194, -1, 0, 0],
376
            'DSTDEVP' => [195, 3, 0, 0],
377
            'DVARP' => [196, 3, 0, 0],
378
            'TRUNC' => [197, -1, 1, 0],
379
            'ISLOGICAL' => [198, 1, 1, 0],
380
            'DCOUNTA' => [199, 3, 0, 0],
381
            'USDOLLAR' => [204, -1, 1, 0],
382
            'FINDB' => [205, -1, 1, 0],
383
            'SEARCHB' => [206, -1, 1, 0],
384
            'REPLACEB' => [207, 4, 1, 0],
385
            'LEFTB' => [208, -1, 1, 0],
386
            'RIGHTB' => [209, -1, 1, 0],
387
            'MIDB' => [210, 3, 1, 0],
388
            'LENB' => [211, 1, 1, 0],
389
            'ROUNDUP' => [212, 2, 1, 0],
390
            'ROUNDDOWN' => [213, 2, 1, 0],
391
            'ASC' => [214, 1, 1, 0],
392
            'DBCS' => [215, 1, 1, 0],
393
            'RANK' => [216, -1, 0, 0],
394
            'ADDRESS' => [219, -1, 1, 0],
395
            'DAYS360' => [220, -1, 1, 0],
396
            'TODAY' => [221, 0, 1, 1],
397
            'VDB' => [222, -1, 1, 0],
398
            'MEDIAN' => [227, -1, 0, 0],
399
            'SUMPRODUCT' => [228, -1, 2, 0],
400
            'SINH' => [229, 1, 1, 0],
401
            'COSH' => [230, 1, 1, 0],
402
            'TANH' => [231, 1, 1, 0],
403
            'ASINH' => [232, 1, 1, 0],
404
            'ACOSH' => [233, 1, 1, 0],
405
            'ATANH' => [234, 1, 1, 0],
406
            'DGET' => [235, 3, 0, 0],
407
            'INFO' => [244, 1, 1, 1],
408
            'DB' => [247, -1, 1, 0],
409
            'FREQUENCY' => [252, 2, 0, 0],
410
            'ERROR.TYPE' => [261, 1, 1, 0],
411
            'REGISTER.ID' => [267, -1, 1, 0],
412
            'AVEDEV' => [269, -1, 0, 0],
413
            'BETADIST' => [270, -1, 1, 0],
414
            'GAMMALN' => [271, 1, 1, 0],
415
            'BETAINV' => [272, -1, 1, 0],
416
            'BINOMDIST' => [273, 4, 1, 0],
417
            'CHIDIST' => [274, 2, 1, 0],
418
            'CHIINV' => [275, 2, 1, 0],
419
            'COMBIN' => [276, 2, 1, 0],
420
            'CONFIDENCE' => [277, 3, 1, 0],
421
            'CRITBINOM' => [278, 3, 1, 0],
422
            'EVEN' => [279, 1, 1, 0],
423
            'EXPONDIST' => [280, 3, 1, 0],
424
            'FDIST' => [281, 3, 1, 0],
425
            'FINV' => [282, 3, 1, 0],
426
            'FISHER' => [283, 1, 1, 0],
427
            'FISHERINV' => [284, 1, 1, 0],
428
            'FLOOR' => [285, 2, 1, 0],
429
            'GAMMADIST' => [286, 4, 1, 0],
430
            'GAMMAINV' => [287, 3, 1, 0],
431
            'CEILING' => [288, 2, 1, 0],
432
            'HYPGEOMDIST' => [289, 4, 1, 0],
433
            'LOGNORMDIST' => [290, 3, 1, 0],
434
            'LOGINV' => [291, 3, 1, 0],
435
            'NEGBINOMDIST' => [292, 3, 1, 0],
436
            'NORMDIST' => [293, 4, 1, 0],
437
            'NORMSDIST' => [294, 1, 1, 0],
438
            'NORMINV' => [295, 3, 1, 0],
439
            'NORMSINV' => [296, 1, 1, 0],
440
            'STANDARDIZE' => [297, 3, 1, 0],
441
            'ODD' => [298, 1, 1, 0],
442
            'PERMUT' => [299, 2, 1, 0],
443
            'POISSON' => [300, 3, 1, 0],
444
            'TDIST' => [301, 3, 1, 0],
445
            'WEIBULL' => [302, 4, 1, 0],
446
            'SUMXMY2' => [303, 2, 2, 0],
447
            'SUMX2MY2' => [304, 2, 2, 0],
448
            'SUMX2PY2' => [305, 2, 2, 0],
449
            'CHITEST' => [306, 2, 2, 0],
450
            'CORREL' => [307, 2, 2, 0],
451
            'COVAR' => [308, 2, 2, 0],
452
            'FORECAST' => [309, 3, 2, 0],
453
            'FTEST' => [310, 2, 2, 0],
454
            'INTERCEPT' => [311, 2, 2, 0],
455
            'PEARSON' => [312, 2, 2, 0],
456
            'RSQ' => [313, 2, 2, 0],
457
            'STEYX' => [314, 2, 2, 0],
458
            'SLOPE' => [315, 2, 2, 0],
459
            'TTEST' => [316, 4, 2, 0],
460
            'PROB' => [317, -1, 2, 0],
461
            'DEVSQ' => [318, -1, 0, 0],
462
            'GEOMEAN' => [319, -1, 0, 0],
463
            'HARMEAN' => [320, -1, 0, 0],
464
            'SUMSQ' => [321, -1, 0, 0],
465
            'KURT' => [322, -1, 0, 0],
466
            'SKEW' => [323, -1, 0, 0],
467
            'ZTEST' => [324, -1, 0, 0],
468
            'LARGE' => [325, 2, 0, 0],
469
            'SMALL' => [326, 2, 0, 0],
470
            'QUARTILE' => [327, 2, 0, 0],
471
            'PERCENTILE' => [328, 2, 0, 0],
472
            'PERCENTRANK' => [329, -1, 0, 0],
473
            'MODE' => [330, -1, 2, 0],
474
            'TRIMMEAN' => [331, 2, 0, 0],
475
            'TINV' => [332, 2, 1, 0],
476
            'CONCATENATE' => [336, -1, 1, 0],
477
            'POWER' => [337, 2, 1, 0],
478
            'RADIANS' => [342, 1, 1, 0],
479
            'DEGREES' => [343, 1, 1, 0],
480
            'SUBTOTAL' => [344, -1, 0, 0],
481
            'SUMIF' => [345, -1, 0, 0],
482
            'COUNTIF' => [346, 2, 0, 0],
483
            'COUNTBLANK' => [347, 1, 0, 0],
484
            'ISPMT' => [350, 4, 1, 0],
485
            'DATEDIF' => [351, 3, 1, 0],
486
            'DATESTRING' => [352, 1, 1, 0],
487
            'NUMBERSTRING' => [353, 2, 1, 0],
488
            'ROMAN' => [354, -1, 1, 0],
489
            'GETPIVOTDATA' => [358, -1, 0, 0],
490
            'HYPERLINK' => [359, -1, 1, 0],
491
            'PHONETIC' => [360, 1, 0, 0],
492
            'AVERAGEA' => [361, -1, 0, 0],
493
            'MAXA' => [362, -1, 0, 0],
494
            'MINA' => [363, -1, 0, 0],
495
            'STDEVPA' => [364, -1, 0, 0],
496
            'VARPA' => [365, -1, 0, 0],
497
            'STDEVA' => [366, -1, 0, 0],
498
            'VARA' => [367, -1, 0, 0],
499
            'BAHTTEXT' => [368, 1, 0, 0],
500
        ];
501 37
    }
502
503
    /**
504
     * Convert a token to the proper ptg value.
505
     *
506
     * @param mixed $token the token to convert
507
     *
508
     * @return mixed the converted token on success
509
     */
510 17
    private function convert($token)
511
    {
512 17
        if (preg_match('/"([^"]|""){0,255}"/', $token)) {
513 8
            return $this->convertString($token);
514 17
        } elseif (is_numeric($token)) {
515 14
            return $this->convertNumber($token);
516
        // match references like A1 or $A$1
517 17
        } elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/', $token)) {
518 11
            return $this->convertRef2d($token);
519
        // match external references like Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
520 17
        } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?[A-Ia-i]?[A-Za-z]\\$?(\d+)$/u", $token)) {
521
            return $this->convertRef3d($token);
522
        // match external references like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
523 17
        } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?[A-Ia-i]?[A-Za-z]\\$?(\d+)$/u", $token)) {
524 1
            return $this->convertRef3d($token);
525
        // match ranges like A1:B2 or $A$1:$B$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
526 17
        } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/', $token)) {
527 14
            return $this->convertRange2d($token);
528
        // match external ranges like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
529 16
        } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?([A-Ia-i]?[A-Za-z])?\\$?(\d+)\:\\$?([A-Ia-i]?[A-Za-z])?\\$?(\d+)$/u", $token)) {
530
            return $this->convertRange3d($token);
531
        // match external ranges like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
532 16
        } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?([A-Ia-i]?[A-Za-z])?\\$?(\d+)\:\\$?([A-Ia-i]?[A-Za-z])?\\$?(\d+)$/u", $token)) {
533 3
            return $this->convertRange3d($token);
534
        // operators (including parentheses)
535 15
        } elseif (isset($this->ptg[$token])) {
536 11
            return pack('C', $this->ptg[$token]);
537
        // match error codes
538 14
        } elseif (preg_match("/^#[A-Z0\/]{3,5}[!?]{1}$/", $token) or $token == '#N/A') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
539
            return $this->convertError($token);
540
        // commented so argument number can be processed correctly. See toReversePolish().
541
        /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/", $token))
0 ignored issues
show
Unused Code Comprehensibility introduced by
71% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
542
        {
543
            return($this->convertFunction($token, $this->_func_args));
544
        }*/
545
        // if it's an argument, ignore the token (the argument remains)
546 14
        } elseif ($token == 'arg') {
547 14
            return '';
548
        }
549
550
        // TODO: use real error codes
551
        throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown token $token");
552
    }
553
554
    /**
555
     * Convert a number token to ptgInt or ptgNum.
556
     *
557
     * @param mixed $num an integer or double for conversion to its ptg value
558
     */
559 14
    private function convertNumber($num)
560
    {
561
        // Integer in the range 0..2**16-1
562 14
        if ((preg_match("/^\d+$/", $num)) and ($num <= 65535)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
563 14
            return pack('Cv', $this->ptg['ptgInt'], $num);
564
        }
565
566
        // A float
567 6
        if (BIFFwriter::getByteOrder()) { // if it's Big Endian
568
            $num = strrev($num);
569
        }
570
571 6
        return pack('Cd', $this->ptg['ptgNum'], $num);
572
    }
573
574
    /**
575
     * Convert a string token to ptgStr.
576
     *
577
     * @param string $string a string for conversion to its ptg value
578
     *
579
     * @return mixed the converted token on success
580
     */
581 8
    private function convertString($string)
582
    {
583
        // chop away beggining and ending quotes
584 8
        $string = substr($string, 1, strlen($string) - 2);
585 8
        if (strlen($string) > 255) {
586
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception('String is too long');
587
        }
588
589 8
        return pack('C', $this->ptg['ptgStr']) . \PhpOffice\PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeShort($string);
590
    }
591
592
    /**
593
     * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
594
     * args that it takes.
595
     *
596
     * @param string $token the name of the function for convertion to ptg value
597
     * @param int $num_args the number of arguments the function receives
598
     *
599
     * @return string The packed ptg for the function
600
     */
601 14
    private function convertFunction($token, $num_args)
602
    {
603 14
        $args = $this->functions[$token][1];
604
605
        // Fixed number of args eg. TIME($i, $j, $k).
606 14
        if ($args >= 0) {
607 3
            return pack('Cv', $this->ptg['ptgFuncV'], $this->functions[$token][0]);
608
        }
609
        // Variable number of args eg. SUM($i, $j, $k, ..).
610 14
        if ($args == -1) {
611 14
            return pack('CCv', $this->ptg['ptgFuncVarV'], $num_args, $this->functions[$token][0]);
612
        }
613
    }
614
615
    /**
616
     * Convert an Excel range such as A1:D4 to a ptgRefV.
617
     *
618
     * @param string $range An Excel range in the A1:A2
619
     * @param int $class
620
     */
621 14
    private function convertRange2d($range, $class = 0)
622
    {
623
        // TODO: possible class value 0,1,2 check Formula.pm
624
        // Split the range into 2 cell refs
625 14
        if (preg_match('/^(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)\:(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)$/', $range)) {
626 14
            list($cell1, $cell2) = explode(':', $range);
627
        } else {
628
            // TODO: use real error codes
629
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception('Unknown range separator');
630
        }
631
632
        // Convert the cell references
633 14
        list($row1, $col1) = $this->cellToPackedRowcol($cell1);
634 14
        list($row2, $col2) = $this->cellToPackedRowcol($cell2);
635
636
        // The ptg value depends on the class of the ptg.
637 14
        if ($class == 0) {
638 14
            $ptgArea = pack('C', $this->ptg['ptgArea']);
639
        } elseif ($class == 1) {
640
            $ptgArea = pack('C', $this->ptg['ptgAreaV']);
641
        } elseif ($class == 2) {
642
            $ptgArea = pack('C', $this->ptg['ptgAreaA']);
643
        } else {
644
            // TODO: use real error codes
645
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown class $class");
646
        }
647
648 14
        return $ptgArea . $row1 . $row2 . $col1 . $col2;
649
    }
650
651
    /**
652
     * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
653
     * a ptgArea3d.
654
     *
655
     * @param string $token an Excel range in the Sheet1!A1:A2 format
656
     *
657
     * @return mixed the packed ptgArea3d token on success
658
     */
659 3
    private function convertRange3d($token)
660
    {
661
        // Split the ref at the ! symbol
662 3
        list($ext_ref, $range) = explode('!', $token);
663
664
        // Convert the external reference part (different for BIFF8)
665 3
        $ext_ref = $this->getRefIndex($ext_ref);
666
667
        // Split the range into 2 cell refs
668 3
        list($cell1, $cell2) = explode(':', $range);
669
670
        // Convert the cell references
671 3
        if (preg_match("/^(\\$)?[A-Ia-i]?[A-Za-z](\\$)?(\d+)$/", $cell1)) {
672 3
            list($row1, $col1) = $this->cellToPackedRowcol($cell1);
673 3
            list($row2, $col2) = $this->cellToPackedRowcol($cell2);
674
        } else { // It's a rows range (like 26:27)
675
            list($row1, $col1, $row2, $col2) = $this->rangeToPackedRange($cell1 . ':' . $cell2);
676
        }
677
678
        // The ptg value depends on the class of the ptg.
679 3
        $ptgArea = pack('C', $this->ptg['ptgArea3d']);
680
681 3
        return $ptgArea . $ext_ref . $row1 . $row2 . $col1 . $col2;
682
    }
683
684
    /**
685
     * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
686
     *
687
     * @param string $cell An Excel cell reference
688
     *
689
     * @return string The cell in packed() format with the corresponding ptg
690
     */
691 11
    private function convertRef2d($cell)
692
    {
693
        // Convert the cell reference
694 11
        $cell_array = $this->cellToPackedRowcol($cell);
695 11
        list($row, $col) = $cell_array;
696
697
        // The ptg value depends on the class of the ptg.
698 11
        $ptgRef = pack('C', $this->ptg['ptgRefA']);
699
700 11
        return $ptgRef . $row . $col;
701
    }
702
703
    /**
704
     * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
705
     * ptgRef3d.
706
     *
707
     * @param string $cell An Excel cell reference
708
     *
709
     * @return mixed the packed ptgRef3d token on success
710
     */
711 1
    private function convertRef3d($cell)
712
    {
713
        // Split the ref at the ! symbol
714 1
        list($ext_ref, $cell) = explode('!', $cell);
715
716
        // Convert the external reference part (different for BIFF8)
717 1
        $ext_ref = $this->getRefIndex($ext_ref);
718
719
        // Convert the cell reference part
720 1
        list($row, $col) = $this->cellToPackedRowcol($cell);
721
722
        // The ptg value depends on the class of the ptg.
723 1
        $ptgRef = pack('C', $this->ptg['ptgRef3dA']);
724
725 1
        return $ptgRef . $ext_ref . $row . $col;
726
    }
727
728
    /**
729
     * Convert an error code to a ptgErr.
730
     *
731
     * @param string $errorCode The error code for conversion to its ptg value
732
     *
733
     * @return string The error code ptgErr
734
     */
735
    private function convertError($errorCode)
736
    {
737
        switch ($errorCode) {
738
            case '#NULL!':
739
                return pack('C', 0x00);
740
            case '#DIV/0!':
741
                return pack('C', 0x07);
742
            case '#VALUE!':
743
                return pack('C', 0x0F);
744
            case '#REF!':
745
                return pack('C', 0x17);
746
            case '#NAME?':
747
                return pack('C', 0x1D);
748
            case '#NUM!':
749
                return pack('C', 0x24);
750
            case '#N/A':
751
                return pack('C', 0x2A);
752
        }
753
754
        return pack('C', 0xFF);
755
    }
756
757
    /**
758
     * Convert the sheet name part of an external reference, for example "Sheet1" or
759
     * "Sheet1:Sheet2", to a packed structure.
760
     *
761
     * @param string $ext_ref The name of the external reference
762
     *
763
     * @return string The reference index in packed() format
764
     */
765
    private function packExtRef($ext_ref)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
766
    {
767
        $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
768
        $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
769
770
        // Check if there is a sheet range eg., Sheet1:Sheet2.
771 View Code Duplication
        if (preg_match('/:/', $ext_ref)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
772
            list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
773
774
            $sheet1 = $this->getSheetIndex($sheet_name1);
775
            if ($sheet1 == -1) {
776
                throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown sheet name $sheet_name1 in formula");
777
            }
778
            $sheet2 = $this->getSheetIndex($sheet_name2);
779
            if ($sheet2 == -1) {
780
                throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown sheet name $sheet_name2 in formula");
781
            }
782
783
            // Reverse max and min sheet numbers if necessary
784
            if ($sheet1 > $sheet2) {
785
                list($sheet1, $sheet2) = [$sheet2, $sheet1];
786
            }
787
        } else { // Single sheet name only.
788
            $sheet1 = $this->getSheetIndex($ext_ref);
789
            if ($sheet1 == -1) {
790
                throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown sheet name $ext_ref in formula");
791
            }
792
            $sheet2 = $sheet1;
793
        }
794
795
        // References are stored relative to 0xFFFF.
796
        $offset = -1 - $sheet1;
797
798
        return pack('vdvv', $offset, 0x00, $sheet1, $sheet2);
799
    }
800
801
    /**
802
     * Look up the REF index that corresponds to an external sheet name
803
     * (or range). If it doesn't exist yet add it to the workbook's references
804
     * array. It assumes all sheet names given must exist.
805
     *
806
     * @param string $ext_ref The name of the external reference
807
     *
808
     * @return mixed The reference index in packed() format on success
809
     */
810 4
    private function getRefIndex($ext_ref)
811
    {
812 4
        $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
813 4
        $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
814 4
        $ext_ref = str_replace('\'\'', '\'', $ext_ref); // Replace escaped '' with '
815
816
        // Check if there is a sheet range eg., Sheet1:Sheet2.
817 4 View Code Duplication
        if (preg_match('/:/', $ext_ref)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
818
            list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
819
820
            $sheet1 = $this->getSheetIndex($sheet_name1);
821
            if ($sheet1 == -1) {
822
                throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown sheet name $sheet_name1 in formula");
823
            }
824
            $sheet2 = $this->getSheetIndex($sheet_name2);
825
            if ($sheet2 == -1) {
826
                throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown sheet name $sheet_name2 in formula");
827
            }
828
829
            // Reverse max and min sheet numbers if necessary
830
            if ($sheet1 > $sheet2) {
831
                list($sheet1, $sheet2) = [$sheet2, $sheet1];
832
            }
833
        } else { // Single sheet name only.
834 4
            $sheet1 = $this->getSheetIndex($ext_ref);
835 4
            if ($sheet1 == -1) {
836
                throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown sheet name $ext_ref in formula");
837
            }
838 4
            $sheet2 = $sheet1;
839
        }
840
841
        // assume all references belong to this document
842 4
        $supbook_index = 0x00;
843 4
        $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
844 4
        $totalreferences = count($this->references);
845 4
        $index = -1;
846 4
        for ($i = 0; $i < $totalreferences; ++$i) {
847 4
            if ($ref == $this->references[$i]) {
848 4
                $index = $i;
849 4
                break;
850
            }
851
        }
852
        // if REF was not found add it to references array
853 4
        if ($index == -1) {
854
            $this->references[$totalreferences] = $ref;
855
            $index = $totalreferences;
856
        }
857
858 4
        return pack('v', $index);
859
    }
860
861
    /**
862
     * Look up the index that corresponds to an external sheet name. The hash of
863
     * sheet names is updated by the addworksheet() method of the
864
     * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class.
865
     *
866
     * @param string $sheet_name Sheet name
867
     *
868
     * @return int The sheet index, -1 if the sheet was not found
869
     */
870 4
    private function getSheetIndex($sheet_name)
871
    {
872 4
        if (!isset($this->externalSheets[$sheet_name])) {
873
            return -1;
874
        }
875
876 4
        return $this->externalSheets[$sheet_name];
877
    }
878
879
    /**
880
     * This method is used to update the array of sheet names. It is
881
     * called by the addWorksheet() method of the
882
     * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class.
883
     *
884
     * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::addWorksheet()
885
     *
886
     * @param string $name The name of the worksheet being added
887
     * @param int $index The index of the worksheet being added
888
     */
889 37
    public function setExtSheet($name, $index)
890
    {
891 37
        $this->externalSheets[$name] = $index;
892 37
    }
893
894
    /**
895
     * pack() row and column into the required 3 or 4 byte format.
896
     *
897
     * @param string $cell The Excel cell reference to be packed
898
     *
899
     * @return array Array containing the row and column in packed() format
900
     */
901 16
    private function cellToPackedRowcol($cell)
902
    {
903 16
        $cell = strtoupper($cell);
904 16
        list($row, $col, $row_rel, $col_rel) = $this->cellToRowcol($cell);
905 16
        if ($col >= 256) {
906
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Column in: $cell greater than 255");
907
        }
908 16
        if ($row >= 65536) {
909
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Row in: $cell greater than 65536 ");
910
        }
911
912
        // Set the high bits to indicate if row or col are relative.
913 16
        $col |= $col_rel << 14;
914 16
        $col |= $row_rel << 15;
915 16
        $col = pack('v', $col);
916
917 16
        $row = pack('v', $row);
918
919 16
        return [$row, $col];
920
    }
921
922
    /**
923
     * pack() row range into the required 3 or 4 byte format.
924
     * Just using maximum col/rows, which is probably not the correct solution.
925
     *
926
     * @param string $range The Excel range to be packed
927
     *
928
     * @return array Array containing (row1,col1,row2,col2) in packed() format
929
     */
930
    private function rangeToPackedRange($range)
931
    {
932
        preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match);
933
        // return absolute rows if there is a $ in the ref
934
        $row1_rel = empty($match[1]) ? 1 : 0;
935
        $row1 = $match[2];
936
        $row2_rel = empty($match[3]) ? 1 : 0;
937
        $row2 = $match[4];
938
        // Convert 1-index to zero-index
939
        --$row1;
940
        --$row2;
941
        // Trick poor inocent Excel
942
        $col1 = 0;
943
        $col2 = 65535; // FIXME: maximum possible value for Excel 5 (change this!!!)
944
945
        // FIXME: this changes for BIFF8
946
        if (($row1 >= 65536) or ($row2 >= 65536)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
947
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Row in: $range greater than 65536 ");
948
        }
949
950
        // Set the high bits to indicate if rows are relative.
951
        $col1 |= $row1_rel << 15;
952
        $col2 |= $row2_rel << 15;
953
        $col1 = pack('v', $col1);
954
        $col2 = pack('v', $col2);
955
956
        $row1 = pack('v', $row1);
957
        $row2 = pack('v', $row2);
958
959
        return [$row1, $col1, $row2, $col2];
960
    }
961
962
    /**
963
     * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
964
     * indexed row and column number. Also returns two (0,1) values to indicate
965
     * whether the row or column are relative references.
966
     *
967
     * @param string $cell the Excel cell reference in A1 format
968
     *
969
     * @return array
970
     */
971 16
    private function cellToRowcol($cell)
972
    {
973 16
        preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/', $cell, $match);
974
        // return absolute column if there is a $ in the ref
975 16
        $col_rel = empty($match[1]) ? 1 : 0;
976 16
        $col_ref = $match[2];
977 16
        $row_rel = empty($match[3]) ? 1 : 0;
978 16
        $row = $match[4];
979
980
        // Convert base26 column string to a number.
981 16
        $expn = strlen($col_ref) - 1;
982 16
        $col = 0;
983 16
        $col_ref_length = strlen($col_ref);
984 16
        for ($i = 0; $i < $col_ref_length; ++$i) {
985 16
            $col += (ord($col_ref[$i]) - 64) * pow(26, $expn);
986 16
            --$expn;
987
        }
988
989
        // Convert 1-index to zero-index
990 16
        --$row;
991 16
        --$col;
992
993 16
        return [$row, $col, $row_rel, $col_rel];
994
    }
995
996
    /**
997
     * Advance to the next valid token.
998
     */
999 17
    private function advance()
1000
    {
1001 17
        $i = $this->currentCharacter;
1002 17
        $formula_length = strlen($this->formula);
1003
        // eat up white spaces
1004 17
        if ($i < $formula_length) {
1005 17
            while ($this->formula[$i] == ' ') {
1006 1
                ++$i;
1007
            }
1008
1009 17
            if ($i < ($formula_length - 1)) {
1010 17
                $this->lookAhead = $this->formula[$i + 1];
1011
            }
1012 17
            $token = '';
1013
        }
1014
1015 17
        while ($i < $formula_length) {
1016 17
            $token .= $this->formula[$i];
0 ignored issues
show
Bug introduced by
The variable $token does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1017
1018 17 View Code Duplication
            if ($i < ($formula_length - 1)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1019 17
                $this->lookAhead = $this->formula[$i + 1];
1020
            } else {
1021 17
                $this->lookAhead = '';
1022
            }
1023
1024 17
            if ($this->match($token) != '') {
1025 17
                $this->currentCharacter = $i + 1;
1026 17
                $this->currentToken = $token;
1027
1028 17
                return 1;
1029
            }
1030
1031 17 View Code Duplication
            if ($i < ($formula_length - 2)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1032 17
                $this->lookAhead = $this->formula[$i + 2];
1033
            } else { // if we run out of characters lookAhead becomes empty
1034 13
                $this->lookAhead = '';
1035
            }
1036 17
            ++$i;
1037
        }
1038
        //die("Lexical error ".$this->currentCharacter);
0 ignored issues
show
Unused Code Comprehensibility introduced by
78% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1039 17
    }
1040
1041
    /**
1042
     * Checks if it's a valid token.
1043
     *
1044
     * @param mixed $token the token to check
1045
     *
1046
     * @return mixed The checked token or false on failure
1047
     */
1048 17
    private function match($token)
1049
    {
1050
        switch ($token) {
1051 17
            case '+':
1052 17
            case '-':
1053 17
            case '*':
1054 17
            case '/':
1055 17
            case '(':
1056 17
            case ')':
1057 17
            case ',':
1058 17
            case ';':
1059 17
            case '>=':
1060 17
            case '<=':
1061 17
            case '=':
1062 17
            case '<>':
1063 17
            case '^':
1064 17
            case '&':
1065 17
            case '%':
1066 16
                return $token;
1067
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1068 17
            case '>':
1069
                if ($this->lookAhead == '=') { // it's a GE token
1070
                    break;
1071
                }
1072
1073
                return $token;
1074
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1075 17
            case '<':
1076
                // it's a LE or a NE token
1077 5
                if (($this->lookAhead == '=') or ($this->lookAhead == '>')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1078 5
                    break;
1079
                }
1080
1081
                return $token;
1082
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1083
            default:
1084
                // if it's a reference A1 or $A$1 or $A1 or A$1
1085 17
                if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/', $token) and !preg_match('/[0-9]/', $this->lookAhead) and ($this->lookAhead != ':') and ($this->lookAhead != '.') and ($this->lookAhead != '!')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1086 11
                    return $token;
1087 17
                } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?[A-Ia-i]?[A-Za-z]\\$?[0-9]+$/u", $token) and !preg_match('/[0-9]/', $this->lookAhead) and ($this->lookAhead != ':') and ($this->lookAhead != '.')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1088
                    // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
1089
                    return $token;
1090 17
                } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?[A-Ia-i]?[A-Za-z]\\$?[0-9]+$/u", $token) and !preg_match('/[0-9]/', $this->lookAhead) and ($this->lookAhead != ':') and ($this->lookAhead != '.')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1091
                    // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
1092 1
                    return $token;
1093 17
                } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/', $token) && !preg_match('/[0-9]/', $this->lookAhead)) {
1094
                    // if it's a range A1:A2 or $A$1:$A$2
1095 14
                    return $token;
1096 17
                } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+:\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+$/u", $token) and !preg_match('/[0-9]/', $this->lookAhead)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1097
                    // If it's an external range like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2
1098
                    return $token;
1099 17
                } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+:\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+$/u", $token) and !preg_match('/[0-9]/', $this->lookAhead)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1100
                    // If it's an external range like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2
1101 3
                    return $token;
1102 17
                } elseif (is_numeric($token) and (!is_numeric($token . $this->lookAhead) or ($this->lookAhead == '')) and ($this->lookAhead != '!') and ($this->lookAhead != ':')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1103
                    // If it's a number (check that it's not a sheet name or range)
1104 9
                    return $token;
1105 17
                } elseif (preg_match('/"([^"]|""){0,255}"/', $token) and $this->lookAhead != '"' and (substr_count($token, '"') % 2 == 0)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1106
                    // If it's a string (of maximum 255 characters)
1107 8
                    return $token;
1108 17
                } elseif (preg_match("/^#[A-Z0\/]{3,5}[!?]{1}$/", $token) or $token == '#N/A') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1109
                    // If it's an error code
1110
                    return $token;
1111 17
                } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i", $token) and ($this->lookAhead == '(')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1112
                    // if it's a function call
1113 14
                    return $token;
1114 17
                } elseif (substr($token, -1) == ')') {
1115
                    //    It's an argument of some description (e.g. a named range),
1116
                    //        precise nature yet to be determined
1117 3
                    return $token;
1118
                }
1119
1120 17
                return '';
1121
        }
1122 5
    }
1123
1124
    /**
1125
     * The parsing method. It parses a formula.
1126
     *
1127
     * @param string $formula the formula to parse, without the initial equal
1128
     *                        sign (=)
1129
     *
1130
     * @return mixed true on success
1131
     */
1132 17
    public function parse($formula)
1133
    {
1134 17
        $this->currentCharacter = 0;
1135 17
        $this->formula = $formula;
1136 17
        $this->lookAhead = isset($formula[1]) ? $formula[1]
1137 1
        : '';
1138 17
        $this->advance();
1139 17
        $this->parseTree = $this->condition();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->condition() of type array is incompatible with the declared type string of property $parseTree.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1140
1141 17
        return true;
1142
    }
1143
1144
    /**
1145
     * It parses a condition. It assumes the following rule:
1146
     * Cond -> Expr [(">" | "<") Expr].
1147
     *
1148
     * @return mixed The parsed ptg'd tree on success
1149
     */
1150 17
    private function condition()
1151
    {
1152 17
        $result = $this->expression();
1153 17
        if ($this->currentToken == '<') {
1154
            $this->advance();
1155
            $result2 = $this->expression();
1156
            $result = $this->createTree('ptgLT', $result, $result2);
1157 17 View Code Duplication
        } elseif ($this->currentToken == '>') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1158
            $this->advance();
1159
            $result2 = $this->expression();
1160
            $result = $this->createTree('ptgGT', $result, $result2);
1161 17
        } elseif ($this->currentToken == '<=') {
1162
            $this->advance();
1163
            $result2 = $this->expression();
1164
            $result = $this->createTree('ptgLE', $result, $result2);
1165 17 View Code Duplication
        } elseif ($this->currentToken == '>=') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1166
            $this->advance();
1167
            $result2 = $this->expression();
1168
            $result = $this->createTree('ptgGE', $result, $result2);
1169 17
        } elseif ($this->currentToken == '=') {
1170
            $this->advance();
1171
            $result2 = $this->expression();
1172
            $result = $this->createTree('ptgEQ', $result, $result2);
1173 17 View Code Duplication
        } elseif ($this->currentToken == '<>') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1174 5
            $this->advance();
1175 5
            $result2 = $this->expression();
1176 5
            $result = $this->createTree('ptgNE', $result, $result2);
1177 17
        } elseif ($this->currentToken == '&') {
1178 5
            $this->advance();
1179 5
            $result2 = $this->expression();
1180 5
            $result = $this->createTree('ptgConcat', $result, $result2);
1181
        }
1182
1183 17
        return $result;
1184
    }
1185
1186
    /**
1187
     * It parses a expression. It assumes the following rule:
1188
     * Expr -> Term [("+" | "-") Term]
1189
     *      -> "string"
1190
     *      -> "-" Term : Negative value
1191
     *      -> "+" Term : Positive value
1192
     *      -> Error code.
1193
     *
1194
     * @return mixed The parsed ptg'd tree on success
1195
     */
1196 17
    private function expression()
1197
    {
1198
        // If it's a string return a string node
1199 17
        if (preg_match('/"([^"]|""){0,255}"/', $this->currentToken)) {
1200 8
            $tmp = str_replace('""', '"', $this->currentToken);
1201 8
            if (($tmp == '"') || ($tmp == '')) {
1202
                //    Trap for "" that has been used for an empty string
1203 5
                $tmp = '""';
1204
            }
1205 8
            $result = $this->createTree($tmp, '', '');
1206 8
            $this->advance();
1207
1208 8
            return $result;
1209
        // If it's an error code
1210 17
        } elseif (preg_match("/^#[A-Z0\/]{3,5}[!?]{1}$/", $this->currentToken) or $this->currentToken == '#N/A') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1211
            $result = $this->createTree($this->currentToken, 'ptgErr', '');
1212
            $this->advance();
1213
1214
            return $result;
1215
        // If it's a negative value
1216 17
        } elseif ($this->currentToken == '-') {
1217
            // catch "-" Term
1218
            $this->advance();
1219
            $result2 = $this->expression();
1220
            $result = $this->createTree('ptgUminus', $result2, '');
1221
1222
            return $result;
1223
        // If it's a positive value
1224 17 View Code Duplication
        } elseif ($this->currentToken == '+') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1225
            // catch "+" Term
1226
            $this->advance();
1227
            $result2 = $this->expression();
1228
            $result = $this->createTree('ptgUplus', $result2, '');
1229
1230
            return $result;
1231
        }
1232 17
        $result = $this->term();
1233 17
        while (($this->currentToken == '+') or
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1234 17
               ($this->currentToken == '-') or
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1235 17
               ($this->currentToken == '^')) {
1236 9
            if ($this->currentToken == '+') {
1237 9
                $this->advance();
1238 9
                $result2 = $this->term();
1239 9
                $result = $this->createTree('ptgAdd', $result, $result2);
1240 3
            } elseif ($this->currentToken == '-') {
1241 3
                $this->advance();
1242 3
                $result2 = $this->term();
1243 3
                $result = $this->createTree('ptgSub', $result, $result2);
1244
            } else {
1245
                $this->advance();
1246
                $result2 = $this->term();
1247
                $result = $this->createTree('ptgPower', $result, $result2);
1248
            }
1249
        }
1250
1251 17
        return $result;
1252
    }
1253
1254
    /**
1255
     * This function just introduces a ptgParen element in the tree, so that Excel
1256
     * doesn't get confused when working with a parenthesized formula afterwards.
1257
     *
1258
     * @see fact()
1259
     *
1260
     * @return array The parsed ptg'd tree
1261
     */
1262
    private function parenthesizedExpression()
1263
    {
1264
        $result = $this->createTree('ptgParen', $this->expression(), '');
1265
1266
        return $result;
1267
    }
1268
1269
    /**
1270
     * It parses a term. It assumes the following rule:
1271
     * Term -> Fact [("*" | "/") Fact].
1272
     *
1273
     * @return mixed The parsed ptg'd tree on success
1274
     */
1275 17
    private function term()
1276
    {
1277 17
        $result = $this->fact();
1278 17
        while (($this->currentToken == '*') or
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1279 17
               ($this->currentToken == '/')) {
1280 9
            if ($this->currentToken == '*') {
1281 9
                $this->advance();
1282 9
                $result2 = $this->fact();
1283 9
                $result = $this->createTree('ptgMul', $result, $result2);
1284
            } else {
1285 3
                $this->advance();
1286 3
                $result2 = $this->fact();
1287 3
                $result = $this->createTree('ptgDiv', $result, $result2);
1288
            }
1289
        }
1290
1291 17
        return $result;
1292
    }
1293
1294
    /**
1295
     * It parses a factor. It assumes the following rule:
1296
     * Fact -> ( Expr )
1297
     *       | CellRef
1298
     *       | CellRange
1299
     *       | Number
1300
     *       | Function.
1301
     *
1302
     * @return mixed The parsed ptg'd tree on success
1303
     */
1304 17
    private function fact()
1305
    {
1306 17
        if ($this->currentToken == '(') {
1307
            $this->advance(); // eat the "("
1308
            $result = $this->parenthesizedExpression();
1309
            if ($this->currentToken != ')') {
1310
                throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("')' token expected.");
1311
            }
1312
            $this->advance(); // eat the ")"
1313
            return $result;
1314
        }
1315
        // if it's a reference
1316 17
        if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/', $this->currentToken)) {
1317 11
            $result = $this->createTree($this->currentToken, '', '');
1318 11
            $this->advance();
1319
1320 11
            return $result;
1321 17 View Code Duplication
        } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?[A-Ia-i]?[A-Za-z]\\$?[0-9]+$/u", $this->currentToken)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1322
            // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
1323
            $result = $this->createTree($this->currentToken, '', '');
1324
            $this->advance();
1325
1326
            return $result;
1327 17
        } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?[A-Ia-i]?[A-Za-z]\\$?[0-9]+$/u", $this->currentToken)) {
1328
            // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
1329 1
            $result = $this->createTree($this->currentToken, '', '');
1330 1
            $this->advance();
1331
1332 1
            return $result;
1333 17
        } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/', $this->currentToken) or
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1334 17
                preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/', $this->currentToken)) {
1335
            // if it's a range A1:B2 or $A$1:$B$2
1336
            // must be an error?
1337 14
            $result = $this->createTree($this->currentToken, '', '');
1338 14
            $this->advance();
1339
1340 14
            return $result;
1341 17 View Code Duplication
        } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+:\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+$/u", $this->currentToken)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1342
            // If it's an external range (Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2)
1343
            // must be an error?
1344
            $result = $this->createTree($this->currentToken, '', '');
1345
            $this->advance();
1346
1347
            return $result;
1348 17
        } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+:\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+$/u", $this->currentToken)) {
1349
            // If it's an external range ('Sheet1'!A1:B2 or 'Sheet1'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1'!$A$1:$B$2)
1350
            // must be an error?
1351 3
            $result = $this->createTree($this->currentToken, '', '');
1352 3
            $this->advance();
1353
1354 3
            return $result;
1355 17
        } elseif (is_numeric($this->currentToken)) {
1356
            // If it's a number or a percent
1357 9
            if ($this->lookAhead == '%') {
1358
                $result = $this->createTree('ptgPercent', $this->currentToken, '');
1359
                $this->advance(); // Skip the percentage operator once we've pre-built that tree
1360
            } else {
1361 9
                $result = $this->createTree($this->currentToken, '', '');
1362
            }
1363 9
            $this->advance();
1364
1365 9
            return $result;
1366 17
        } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i", $this->currentToken)) {
1367
            // if it's a function call
1368 14
            $result = $this->func();
1369
1370 14
            return $result;
1371
        }
1372 6
        throw new \PhpOffice\PhpSpreadsheet\Writer\Exception('Syntax error: ' . $this->currentToken . ', lookahead: ' . $this->lookAhead . ', current char: ' . $this->currentCharacter);
1373
    }
1374
1375
    /**
1376
     * It parses a function call. It assumes the following rule:
1377
     * Func -> ( Expr [,Expr]* ).
1378
     *
1379
     * @return mixed The parsed ptg'd tree on success
1380
     */
1381 14
    private function func()
1382
    {
1383 14
        $num_args = 0; // number of arguments received
1384 14
        $function = strtoupper($this->currentToken);
1385 14
        $result = ''; // initialize result
1386 14
        $this->advance();
1387 14
        $this->advance(); // eat the "("
1388 14
        while ($this->currentToken != ')') {
1389 14
            if ($num_args > 0) {
1390 6
                if ($this->currentToken == ',' || $this->currentToken == ';') {
1391 6
                    $this->advance(); // eat the "," or ";"
1392
                } else {
1393
                    throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Syntax error: comma expected in function $function, arg #{$num_args}");
1394
                }
1395 6
                $result2 = $this->condition();
1396 6
                $result = $this->createTree('arg', $result, $result2);
1397
            } else { // first argument
1398 14
                $result2 = $this->condition();
1399 14
                $result = $this->createTree('arg', '', $result2);
1400
            }
1401 14
            ++$num_args;
1402
        }
1403 14
        if (!isset($this->functions[$function])) {
1404
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Function $function() doesn't exist");
1405
        }
1406 14
        $args = $this->functions[$function][1];
1407
        // If fixed number of args eg. TIME($i, $j, $k). Check that the number of args is valid.
1408 14
        if (($args >= 0) and ($args != $num_args)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1409
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Incorrect number of arguments in function $function() ");
1410
        }
1411
1412 14
        $result = $this->createTree($function, $result, $num_args);
1413 14
        $this->advance(); // eat the ")"
1414 14
        return $result;
1415
    }
1416
1417
    /**
1418
     * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
1419
     * as elements.
1420
     *
1421
     * @param mixed $value the value of this node
1422
     * @param mixed $left the left array (sub-tree) or a final node
1423
     * @param mixed $right the right array (sub-tree) or a final node
1424
     *
1425
     * @return array A tree
1426
     */
1427 17
    private function createTree($value, $left, $right)
1428
    {
1429 17
        return ['value' => $value, 'left' => $left, 'right' => $right];
1430
    }
1431
1432
    /**
1433
     * Builds a string containing the tree in reverse polish notation (What you
1434
     * would use in a HP calculator stack).
1435
     * The following tree:.
1436
     *
1437
     *    +
1438
     *   / \
1439
     *  2   3
1440
     *
1441
     * produces: "23+"
1442
     *
1443
     * The following tree:
1444
     *
1445
     *    +
1446
     *   / \
1447
     *  3   *
1448
     *     / \
1449
     *    6   A1
1450
     *
1451
     * produces: "36A1*+"
1452
     *
1453
     * In fact all operands, functions, references, etc... are written as ptg's
1454
     *
1455
     * @param array $tree the optional tree to convert
1456
     *
1457
     * @return string The tree in reverse polish notation
1458
     */
1459 17
    public function toReversePolish($tree = [])
1460
    {
1461 17
        $polish = ''; // the string we are going to return
1462 17
        if (empty($tree)) { // If it's the first call use parseTree
1463 17
            $tree = $this->parseTree;
1464
        }
1465
1466 17 View Code Duplication
        if (is_array($tree['left'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1467 15
            $converted_tree = $this->toReversePolish($tree['left']);
1468 15
            $polish .= $converted_tree;
1469 17
        } elseif ($tree['left'] != '') { // It's a final node
1470
            $converted_tree = $this->convert($tree['left']);
1471
            $polish .= $converted_tree;
1472
        }
1473 17 View Code Duplication
        if (is_array($tree['right'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1474 15
            $converted_tree = $this->toReversePolish($tree['right']);
1475 15
            $polish .= $converted_tree;
1476 17
        } elseif ($tree['right'] != '') { // It's a final node
1477 14
            $converted_tree = $this->convert($tree['right']);
1478 14
            $polish .= $converted_tree;
1479
        }
1480
        // if it's a function convert it here (so we can set it's arguments)
1481 17
        if (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/", $tree['value']) and
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1482 15
            !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/', $tree['value']) and
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1483 14
            !preg_match("/^[A-Ia-i]?[A-Za-z](\d+)\.\.[A-Ia-i]?[A-Za-z](\d+)$/", $tree['value']) and
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1484 14
            !is_numeric($tree['value']) and
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1485 14
            !isset($this->ptg[$tree['value']])) {
1486
            // left subtree for a function is always an array.
1487 14
            if ($tree['left'] != '') {
1488 14
                $left_tree = $this->toReversePolish($tree['left']);
1489
            } else {
1490 3
                $left_tree = '';
1491
            }
1492
            // add it's left subtree and return.
1493 14
            return $left_tree . $this->convertFunction($tree['value'], $tree['right']);
1494
        }
1495 17
        $converted_tree = $this->convert($tree['value']);
1496
1497 17
        $polish .= $converted_tree;
1498
1499 17
        return $polish;
1500
    }
1501
}
1502