Completed
Push — develop ( 91417a...f02c33 )
by Adrien
18:11
created

Parser::convertRef3d()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 16
ccs 0
cts 8
cp 0
crap 2
rs 9.4285
1
<?php
2
3
namespace PhpSpreadsheet\Writer\Excel5;
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
 * @copyright  Copyright (c) 2006 - 2015 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
24
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
25
 * @version    ##VERSION##, ##DATE##
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
     * @var int
70
     */
71
    public $currentCharacter;
72
73
    /**
74
     * The token we are working on.
75
     * @var string
76
     */
77
    public $currentToken;
78
79
    /**
80
     * The formula to parse
81
     * @var string
82
     */
83
    private $formula;
84
85
    /**
86
     * The character ahead of the current char
87
     * @var string
88
     */
89
    public $lookAhead;
90
91
    /**
92
     * The parse tree to be generated
93
     * @var string
94
     */
95
    private $parseTree;
96
97
    /**
98
     * Array of external sheets
99
     * @var array
100
     */
101
    private $externalSheets;
102
103
    /**
104
     * Array of sheet references in the form of REF structures
105
     * @var array
106
     */
107
    public $references;
108
109
    /**
110
     * The class constructor
111
     */
112
    public function __construct()
113
    {
114
        $this->currentCharacter = 0;
115
        $this->currentToken = ''; // The token we are working on.
116
        $this->formula = ''; // The formula to parse.
117
        $this->lookAhead = ''; // The character ahead of the current char.
118
        $this->parseTree = ''; // The parse tree to be generated.
119
        $this->initializeHashes(); // Initialize the hashes: ptg's and function's ptg's
120
        $this->externalSheets = [];
121
        $this->references = [];
122
    }
123
124
    /**
125
     * Initialize the ptg and function hashes.
126
     */
127
    private function initializeHashes()
128
    {
129
        // The Excel ptg indices
130
        $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...
131
            'ptgExp' => 0x01,
132
            'ptgTbl' => 0x02,
133
            'ptgAdd' => 0x03,
134
            'ptgSub' => 0x04,
135
            'ptgMul' => 0x05,
136
            'ptgDiv' => 0x06,
137
            'ptgPower' => 0x07,
138
            'ptgConcat' => 0x08,
139
            'ptgLT' => 0x09,
140
            'ptgLE' => 0x0A,
141
            'ptgEQ' => 0x0B,
142
            'ptgGE' => 0x0C,
143
            'ptgGT' => 0x0D,
144
            'ptgNE' => 0x0E,
145
            'ptgIsect' => 0x0F,
146
            'ptgUnion' => 0x10,
147
            'ptgRange' => 0x11,
148
            'ptgUplus' => 0x12,
149
            'ptgUminus' => 0x13,
150
            'ptgPercent' => 0x14,
151
            'ptgParen' => 0x15,
152
            'ptgMissArg' => 0x16,
153
            'ptgStr' => 0x17,
154
            'ptgAttr' => 0x19,
155
            'ptgSheet' => 0x1A,
156
            'ptgEndSheet' => 0x1B,
157
            'ptgErr' => 0x1C,
158
            'ptgBool' => 0x1D,
159
            'ptgInt' => 0x1E,
160
            'ptgNum' => 0x1F,
161
            'ptgArray' => 0x20,
162
            'ptgFunc' => 0x21,
163
            'ptgFuncVar' => 0x22,
164
            'ptgName' => 0x23,
165
            'ptgRef' => 0x24,
166
            'ptgArea' => 0x25,
167
            'ptgMemArea' => 0x26,
168
            'ptgMemErr' => 0x27,
169
            'ptgMemNoMem' => 0x28,
170
            'ptgMemFunc' => 0x29,
171
            'ptgRefErr' => 0x2A,
172
            'ptgAreaErr' => 0x2B,
173
            'ptgRefN' => 0x2C,
174
            'ptgAreaN' => 0x2D,
175
            'ptgMemAreaN' => 0x2E,
176
            'ptgMemNoMemN' => 0x2F,
177
            'ptgNameX' => 0x39,
178
            'ptgRef3d' => 0x3A,
179
            'ptgArea3d' => 0x3B,
180
            'ptgRefErr3d' => 0x3C,
181
            'ptgAreaErr3d' => 0x3D,
182
            'ptgArrayV' => 0x40,
183
            'ptgFuncV' => 0x41,
184
            'ptgFuncVarV' => 0x42,
185
            'ptgNameV' => 0x43,
186
            'ptgRefV' => 0x44,
187
            'ptgAreaV' => 0x45,
188
            'ptgMemAreaV' => 0x46,
189
            'ptgMemErrV' => 0x47,
190
            'ptgMemNoMemV' => 0x48,
191
            'ptgMemFuncV' => 0x49,
192
            'ptgRefErrV' => 0x4A,
193
            'ptgAreaErrV' => 0x4B,
194
            'ptgRefNV' => 0x4C,
195
            'ptgAreaNV' => 0x4D,
196
            'ptgMemAreaNV' => 0x4E,
197
            'ptgMemNoMemN' => 0x4F,
198
            'ptgFuncCEV' => 0x58,
199
            'ptgNameXV' => 0x59,
200
            'ptgRef3dV' => 0x5A,
201
            'ptgArea3dV' => 0x5B,
202
            'ptgRefErr3dV' => 0x5C,
203
            'ptgAreaErr3d' => 0x5D,
204
            'ptgArrayA' => 0x60,
205
            'ptgFuncA' => 0x61,
206
            'ptgFuncVarA' => 0x62,
207
            'ptgNameA' => 0x63,
208
            'ptgRefA' => 0x64,
209
            'ptgAreaA' => 0x65,
210
            'ptgMemAreaA' => 0x66,
211
            'ptgMemErrA' => 0x67,
212
            'ptgMemNoMemA' => 0x68,
213
            'ptgMemFuncA' => 0x69,
214
            'ptgRefErrA' => 0x6A,
215
            'ptgAreaErrA' => 0x6B,
216
            'ptgRefNA' => 0x6C,
217
            'ptgAreaNA' => 0x6D,
218
            'ptgMemAreaNA' => 0x6E,
219
            'ptgMemNoMemN' => 0x6F,
220
            'ptgFuncCEA' => 0x78,
221
            'ptgNameXA' => 0x79,
222
            'ptgRef3dA' => 0x7A,
223
            'ptgArea3dA' => 0x7B,
224
            'ptgRefErr3dA' => 0x7C,
225
            'ptgAreaErr3d' => 0x7D,
226
        ];
227
228
        // Thanks to Michael Meeks and Gnumeric for the initial arg values.
229
        //
230
        // The following hash was generated by "function_locale.pl" in the distro.
231
        // Refer to function_locale.pl for non-English function names.
232
        //
233
        // The array elements are as follow:
234
        // ptg:   The Excel function ptg code.
235
        // args:  The number of arguments that the function takes:
236
        //           >=0 is a fixed number of arguments.
237
        //           -1  is a variable  number of arguments.
238
        // class: The reference, value or array class of the function args.
239
        // vol:   The function is volatile.
240
        //
241
        $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...
242
            // function                  ptg  args  class  vol
243
            'COUNT' => [0, -1, 0, 0],
244
            'IF' => [1, -1, 1, 0],
245
            'ISNA' => [2, 1, 1, 0],
246
            'ISERROR' => [3, 1, 1, 0],
247
            'SUM' => [4, -1, 0, 0],
248
            'AVERAGE' => [5, -1, 0, 0],
249
            'MIN' => [6, -1, 0, 0],
250
            'MAX' => [7, -1, 0, 0],
251
            'ROW' => [8, -1, 0, 0],
252
            'COLUMN' => [9, -1, 0, 0],
253
            'NA' => [10, 0, 0, 0],
254
            'NPV' => [11, -1, 1, 0],
255
            'STDEV' => [12, -1, 0, 0],
256
            'DOLLAR' => [13, -1, 1, 0],
257
            'FIXED' => [14, -1, 1, 0],
258
            'SIN' => [15, 1, 1, 0],
259
            'COS' => [16, 1, 1, 0],
260
            'TAN' => [17, 1, 1, 0],
261
            'ATAN' => [18, 1, 1, 0],
262
            'PI' => [19, 0, 1, 0],
263
            'SQRT' => [20, 1, 1, 0],
264
            'EXP' => [21, 1, 1, 0],
265
            'LN' => [22, 1, 1, 0],
266
            'LOG10' => [23, 1, 1, 0],
267
            'ABS' => [24, 1, 1, 0],
268
            'INT' => [25, 1, 1, 0],
269
            'SIGN' => [26, 1, 1, 0],
270
            'ROUND' => [27, 2, 1, 0],
271
            'LOOKUP' => [28, -1, 0, 0],
272
            'INDEX' => [29, -1, 0, 1],
273
            'REPT' => [30, 2, 1, 0],
274
            'MID' => [31, 3, 1, 0],
275
            'LEN' => [32, 1, 1, 0],
276
            'VALUE' => [33, 1, 1, 0],
277
            'TRUE' => [34, 0, 1, 0],
278
            'FALSE' => [35, 0, 1, 0],
279
            'AND' => [36, -1, 0, 0],
280
            'OR' => [37, -1, 0, 0],
281
            'NOT' => [38, 1, 1, 0],
282
            'MOD' => [39, 2, 1, 0],
283
            'DCOUNT' => [40, 3, 0, 0],
284
            'DSUM' => [41, 3, 0, 0],
285
            'DAVERAGE' => [42, 3, 0, 0],
286
            'DMIN' => [43, 3, 0, 0],
287
            'DMAX' => [44, 3, 0, 0],
288
            'DSTDEV' => [45, 3, 0, 0],
289
            'VAR' => [46, -1, 0, 0],
290
            'DVAR' => [47, 3, 0, 0],
291
            'TEXT' => [48, 2, 1, 0],
292
            'LINEST' => [49, -1, 0, 0],
293
            'TREND' => [50, -1, 0, 0],
294
            'LOGEST' => [51, -1, 0, 0],
295
            'GROWTH' => [52, -1, 0, 0],
296
            'PV' => [56, -1, 1, 0],
297
            'FV' => [57, -1, 1, 0],
298
            'NPER' => [58, -1, 1, 0],
299
            'PMT' => [59, -1, 1, 0],
300
            'RATE' => [60, -1, 1, 0],
301
            'MIRR' => [61, 3, 0, 0],
302
            'IRR' => [62, -1, 0, 0],
303
            'RAND' => [63, 0, 1, 1],
304
            'MATCH' => [64, -1, 0, 0],
305
            'DATE' => [65, 3, 1, 0],
306
            'TIME' => [66, 3, 1, 0],
307
            'DAY' => [67, 1, 1, 0],
308
            'MONTH' => [68, 1, 1, 0],
309
            'YEAR' => [69, 1, 1, 0],
310
            'WEEKDAY' => [70, -1, 1, 0],
311
            'HOUR' => [71, 1, 1, 0],
312
            'MINUTE' => [72, 1, 1, 0],
313
            'SECOND' => [73, 1, 1, 0],
314
            'NOW' => [74, 0, 1, 1],
315
            'AREAS' => [75, 1, 0, 1],
316
            'ROWS' => [76, 1, 0, 1],
317
            'COLUMNS' => [77, 1, 0, 1],
318
            'OFFSET' => [78, -1, 0, 1],
319
            'SEARCH' => [82, -1, 1, 0],
320
            'TRANSPOSE' => [83, 1, 1, 0],
321
            'TYPE' => [86, 1, 1, 0],
322
            'ATAN2' => [97, 2, 1, 0],
323
            'ASIN' => [98, 1, 1, 0],
324
            'ACOS' => [99, 1, 1, 0],
325
            'CHOOSE' => [100, -1, 1, 0],
326
            'HLOOKUP' => [101, -1, 0, 0],
327
            'VLOOKUP' => [102, -1, 0, 0],
328
            'ISREF' => [105, 1, 0, 0],
329
            'LOG' => [109, -1, 1, 0],
330
            'CHAR' => [111, 1, 1, 0],
331
            'LOWER' => [112, 1, 1, 0],
332
            'UPPER' => [113, 1, 1, 0],
333
            'PROPER' => [114, 1, 1, 0],
334
            'LEFT' => [115, -1, 1, 0],
335
            'RIGHT' => [116, -1, 1, 0],
336
            'EXACT' => [117, 2, 1, 0],
337
            'TRIM' => [118, 1, 1, 0],
338
            'REPLACE' => [119, 4, 1, 0],
339
            'SUBSTITUTE' => [120, -1, 1, 0],
340
            'CODE' => [121, 1, 1, 0],
341
            'FIND' => [124, -1, 1, 0],
342
            'CELL' => [125, -1, 0, 1],
343
            'ISERR' => [126, 1, 1, 0],
344
            'ISTEXT' => [127, 1, 1, 0],
345
            'ISNUMBER' => [128, 1, 1, 0],
346
            'ISBLANK' => [129, 1, 1, 0],
347
            'T' => [130, 1, 0, 0],
348
            'N' => [131, 1, 0, 0],
349
            'DATEVALUE' => [140, 1, 1, 0],
350
            'TIMEVALUE' => [141, 1, 1, 0],
351
            'SLN' => [142, 3, 1, 0],
352
            'SYD' => [143, 4, 1, 0],
353
            'DDB' => [144, -1, 1, 0],
354
            'INDIRECT' => [148, -1, 1, 1],
355
            'CALL' => [150, -1, 1, 0],
356
            'CLEAN' => [162, 1, 1, 0],
357
            'MDETERM' => [163, 1, 2, 0],
358
            'MINVERSE' => [164, 1, 2, 0],
359
            'MMULT' => [165, 2, 2, 0],
360
            'IPMT' => [167, -1, 1, 0],
361
            'PPMT' => [168, -1, 1, 0],
362
            'COUNTA' => [169, -1, 0, 0],
363
            'PRODUCT' => [183, -1, 0, 0],
364
            'FACT' => [184, 1, 1, 0],
365
            'DPRODUCT' => [189, 3, 0, 0],
366
            'ISNONTEXT' => [190, 1, 1, 0],
367
            'STDEVP' => [193, -1, 0, 0],
368
            'VARP' => [194, -1, 0, 0],
369
            'DSTDEVP' => [195, 3, 0, 0],
370
            'DVARP' => [196, 3, 0, 0],
371
            'TRUNC' => [197, -1, 1, 0],
372
            'ISLOGICAL' => [198, 1, 1, 0],
373
            'DCOUNTA' => [199, 3, 0, 0],
374
            'USDOLLAR' => [204, -1, 1, 0],
375
            'FINDB' => [205, -1, 1, 0],
376
            'SEARCHB' => [206, -1, 1, 0],
377
            'REPLACEB' => [207, 4, 1, 0],
378
            'LEFTB' => [208, -1, 1, 0],
379
            'RIGHTB' => [209, -1, 1, 0],
380
            'MIDB' => [210, 3, 1, 0],
381
            'LENB' => [211, 1, 1, 0],
382
            'ROUNDUP' => [212, 2, 1, 0],
383
            'ROUNDDOWN' => [213, 2, 1, 0],
384
            'ASC' => [214, 1, 1, 0],
385
            'DBCS' => [215, 1, 1, 0],
386
            'RANK' => [216, -1, 0, 0],
387
            'ADDRESS' => [219, -1, 1, 0],
388
            'DAYS360' => [220, -1, 1, 0],
389
            'TODAY' => [221, 0, 1, 1],
390
            'VDB' => [222, -1, 1, 0],
391
            'MEDIAN' => [227, -1, 0, 0],
392
            'SUMPRODUCT' => [228, -1, 2, 0],
393
            'SINH' => [229, 1, 1, 0],
394
            'COSH' => [230, 1, 1, 0],
395
            'TANH' => [231, 1, 1, 0],
396
            'ASINH' => [232, 1, 1, 0],
397
            'ACOSH' => [233, 1, 1, 0],
398
            'ATANH' => [234, 1, 1, 0],
399
            'DGET' => [235, 3, 0, 0],
400
            'INFO' => [244, 1, 1, 1],
401
            'DB' => [247, -1, 1, 0],
402
            'FREQUENCY' => [252, 2, 0, 0],
403
            'ERROR.TYPE' => [261, 1, 1, 0],
404
            'REGISTER.ID' => [267, -1, 1, 0],
405
            'AVEDEV' => [269, -1, 0, 0],
406
            'BETADIST' => [270, -1, 1, 0],
407
            'GAMMALN' => [271, 1, 1, 0],
408
            'BETAINV' => [272, -1, 1, 0],
409
            'BINOMDIST' => [273, 4, 1, 0],
410
            'CHIDIST' => [274, 2, 1, 0],
411
            'CHIINV' => [275, 2, 1, 0],
412
            'COMBIN' => [276, 2, 1, 0],
413
            'CONFIDENCE' => [277, 3, 1, 0],
414
            'CRITBINOM' => [278, 3, 1, 0],
415
            'EVEN' => [279, 1, 1, 0],
416
            'EXPONDIST' => [280, 3, 1, 0],
417
            'FDIST' => [281, 3, 1, 0],
418
            'FINV' => [282, 3, 1, 0],
419
            'FISHER' => [283, 1, 1, 0],
420
            'FISHERINV' => [284, 1, 1, 0],
421
            'FLOOR' => [285, 2, 1, 0],
422
            'GAMMADIST' => [286, 4, 1, 0],
423
            'GAMMAINV' => [287, 3, 1, 0],
424
            'CEILING' => [288, 2, 1, 0],
425
            'HYPGEOMDIST' => [289, 4, 1, 0],
426
            'LOGNORMDIST' => [290, 3, 1, 0],
427
            'LOGINV' => [291, 3, 1, 0],
428
            'NEGBINOMDIST' => [292, 3, 1, 0],
429
            'NORMDIST' => [293, 4, 1, 0],
430
            'NORMSDIST' => [294, 1, 1, 0],
431
            'NORMINV' => [295, 3, 1, 0],
432
            'NORMSINV' => [296, 1, 1, 0],
433
            'STANDARDIZE' => [297, 3, 1, 0],
434
            'ODD' => [298, 1, 1, 0],
435
            'PERMUT' => [299, 2, 1, 0],
436
            'POISSON' => [300, 3, 1, 0],
437
            'TDIST' => [301, 3, 1, 0],
438
            'WEIBULL' => [302, 4, 1, 0],
439
            'SUMXMY2' => [303, 2, 2, 0],
440
            'SUMX2MY2' => [304, 2, 2, 0],
441
            'SUMX2PY2' => [305, 2, 2, 0],
442
            'CHITEST' => [306, 2, 2, 0],
443
            'CORREL' => [307, 2, 2, 0],
444
            'COVAR' => [308, 2, 2, 0],
445
            'FORECAST' => [309, 3, 2, 0],
446
            'FTEST' => [310, 2, 2, 0],
447
            'INTERCEPT' => [311, 2, 2, 0],
448
            'PEARSON' => [312, 2, 2, 0],
449
            'RSQ' => [313, 2, 2, 0],
450
            'STEYX' => [314, 2, 2, 0],
451
            'SLOPE' => [315, 2, 2, 0],
452
            'TTEST' => [316, 4, 2, 0],
453
            'PROB' => [317, -1, 2, 0],
454
            'DEVSQ' => [318, -1, 0, 0],
455
            'GEOMEAN' => [319, -1, 0, 0],
456
            'HARMEAN' => [320, -1, 0, 0],
457
            'SUMSQ' => [321, -1, 0, 0],
458
            'KURT' => [322, -1, 0, 0],
459
            'SKEW' => [323, -1, 0, 0],
460
            'ZTEST' => [324, -1, 0, 0],
461
            'LARGE' => [325, 2, 0, 0],
462
            'SMALL' => [326, 2, 0, 0],
463
            'QUARTILE' => [327, 2, 0, 0],
464
            'PERCENTILE' => [328, 2, 0, 0],
465
            'PERCENTRANK' => [329, -1, 0, 0],
466
            'MODE' => [330, -1, 2, 0],
467
            'TRIMMEAN' => [331, 2, 0, 0],
468
            'TINV' => [332, 2, 1, 0],
469
            'CONCATENATE' => [336, -1, 1, 0],
470
            'POWER' => [337, 2, 1, 0],
471
            'RADIANS' => [342, 1, 1, 0],
472
            'DEGREES' => [343, 1, 1, 0],
473
            'SUBTOTAL' => [344, -1, 0, 0],
474
            'SUMIF' => [345, -1, 0, 0],
475
            'COUNTIF' => [346, 2, 0, 0],
476
            'COUNTBLANK' => [347, 1, 0, 0],
477
            'ISPMT' => [350, 4, 1, 0],
478
            'DATEDIF' => [351, 3, 1, 0],
479
            'DATESTRING' => [352, 1, 1, 0],
480
            'NUMBERSTRING' => [353, 2, 1, 0],
481
            'ROMAN' => [354, -1, 1, 0],
482
            'GETPIVOTDATA' => [358, -1, 0, 0],
483
            'HYPERLINK' => [359, -1, 1, 0],
484
            'PHONETIC' => [360, 1, 0, 0],
485
            'AVERAGEA' => [361, -1, 0, 0],
486
            'MAXA' => [362, -1, 0, 0],
487
            'MINA' => [363, -1, 0, 0],
488
            'STDEVPA' => [364, -1, 0, 0],
489
            'VARPA' => [365, -1, 0, 0],
490
            'STDEVA' => [366, -1, 0, 0],
491
            'VARA' => [367, -1, 0, 0],
492
            'BAHTTEXT' => [368, 1, 0, 0],
493
        ];
494
    }
495
496
    /**
497
     * Convert a token to the proper ptg value.
498
     *
499
     * @param mixed $token The token to convert.
500
     * @return mixed the converted token on success
501
     */
502
    private function convert($token)
503
    {
504
        if (preg_match('/"([^"]|""){0,255}"/', $token)) {
505
            return $this->convertString($token);
506
        } elseif (is_numeric($token)) {
507
            return $this->convertNumber($token);
508
        // match references like A1 or $A$1
509
        } elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/', $token)) {
510
            return $this->convertRef2d($token);
511
        // 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...
512
        } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?[A-Ia-i]?[A-Za-z]\\$?(\d+)$/u", $token)) {
513
            return $this->convertRef3d($token);
514
        // 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...
515
        } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?[A-Ia-i]?[A-Za-z]\\$?(\d+)$/u", $token)) {
516
            return $this->convertRef3d($token);
517
        // 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...
518
        } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/', $token)) {
519
            return $this->convertRange2d($token);
520
        // 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...
521
        } 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)) {
522
            return $this->convertRange3d($token);
523
        // 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...
524
        } 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)) {
525
            return $this->convertRange3d($token);
526
        // operators (including parentheses)
527
        } elseif (isset($this->ptg[$token])) {
528
            return pack('C', $this->ptg[$token]);
529
        // match error codes
530
        } 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...
531
            return $this->convertError($token);
532
        // commented so argument number can be processed correctly. See toReversePolish().
533
        /*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...
534
        {
535
            return($this->convertFunction($token, $this->_func_args));
536
        }*/
537
        // if it's an argument, ignore the token (the argument remains)
538
        } elseif ($token == 'arg') {
539
            return '';
540
        }
541
542
        // TODO: use real error codes
543
        throw new \PhpSpreadsheet\Writer\Exception("Unknown token $token");
544
    }
545
546
    /**
547
     * Convert a number token to ptgInt or ptgNum
548
     *
549
     * @param mixed $num an integer or double for conversion to its ptg value
550
     */
551
    private function convertNumber($num)
552
    {
553
        // Integer in the range 0..2**16-1
554
        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...
555
            return pack('Cv', $this->ptg['ptgInt'], $num);
556
        } else { // A float
557
            if (BIFFwriter::getByteOrder()) { // if it's Big Endian
558
                $num = strrev($num);
559
            }
560
561
            return pack('Cd', $this->ptg['ptgNum'], $num);
562
        }
563
    }
564
565
    /**
566
     * Convert a string token to ptgStr
567
     *
568
     * @param string $string A string for conversion to its ptg value.
569
     * @return mixed the converted token on success
570
     */
571
    private function convertString($string)
572
    {
573
        // chop away beggining and ending quotes
574
        $string = substr($string, 1, strlen($string) - 2);
575
        if (strlen($string) > 255) {
576
            throw new \PhpSpreadsheet\Writer\Exception('String is too long');
577
        }
578
579
        return pack('C', $this->ptg['ptgStr']) . \PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeShort($string);
580
    }
581
582
    /**
583
     * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
584
     * args that it takes.
585
     *
586
     * @param string  $token    The name of the function for convertion to ptg value.
587
     * @param int $num_args The number of arguments the function receives.
588
     * @return string The packed ptg for the function
589
     */
590
    private function convertFunction($token, $num_args)
591
    {
592
        $args = $this->functions[$token][1];
593
594
        // Fixed number of args eg. TIME($i, $j, $k).
595
        if ($args >= 0) {
596
            return pack('Cv', $this->ptg['ptgFuncV'], $this->functions[$token][0]);
597
        }
598
        // Variable number of args eg. SUM($i, $j, $k, ..).
599
        if ($args == -1) {
600
            return pack('CCv', $this->ptg['ptgFuncVarV'], $num_args, $this->functions[$token][0]);
601
        }
602
    }
603
604
    /**
605
     * Convert an Excel range such as A1:D4 to a ptgRefV.
606
     *
607
     * @param string    $range    An Excel range in the A1:A2
608
     * @param int        $class
609
     */
610
    private function convertRange2d($range, $class = 0)
611
    {
612
613
        // TODO: possible class value 0,1,2 check Formula.pm
614
        // Split the range into 2 cell refs
615
        if (preg_match('/^(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)\:(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)$/', $range)) {
616
            list($cell1, $cell2) = explode(':', $range);
617
        } else {
618
            // TODO: use real error codes
619
            throw new \PhpSpreadsheet\Writer\Exception('Unknown range separator');
620
        }
621
622
        // Convert the cell references
623
        list($row1, $col1) = $this->cellToPackedRowcol($cell1);
624
        list($row2, $col2) = $this->cellToPackedRowcol($cell2);
625
626
        // The ptg value depends on the class of the ptg.
627
        if ($class == 0) {
628
            $ptgArea = pack('C', $this->ptg['ptgArea']);
629
        } elseif ($class == 1) {
630
            $ptgArea = pack('C', $this->ptg['ptgAreaV']);
631
        } elseif ($class == 2) {
632
            $ptgArea = pack('C', $this->ptg['ptgAreaA']);
633
        } else {
634
            // TODO: use real error codes
635
            throw new \PhpSpreadsheet\Writer\Exception("Unknown class $class");
636
        }
637
638
        return $ptgArea . $row1 . $row2 . $col1 . $col2;
639
    }
640
641
    /**
642
     * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
643
     * a ptgArea3d.
644
     *
645
     * @param string $token An Excel range in the Sheet1!A1:A2 format.
646
     * @return mixed The packed ptgArea3d token on success.
647
     */
648
    private function convertRange3d($token)
649
    {
650
        // Split the ref at the ! symbol
651
        list($ext_ref, $range) = explode('!', $token);
652
653
        // Convert the external reference part (different for BIFF8)
654
        $ext_ref = $this->getRefIndex($ext_ref);
655
656
        // Split the range into 2 cell refs
657
        list($cell1, $cell2) = explode(':', $range);
658
659
        // Convert the cell references
660
        if (preg_match("/^(\\$)?[A-Ia-i]?[A-Za-z](\\$)?(\d+)$/", $cell1)) {
661
            list($row1, $col1) = $this->cellToPackedRowcol($cell1);
662
            list($row2, $col2) = $this->cellToPackedRowcol($cell2);
663
        } else { // It's a rows range (like 26:27)
664
            list($row1, $col1, $row2, $col2) = $this->rangeToPackedRange($cell1 . ':' . $cell2);
665
        }
666
667
        // The ptg value depends on the class of the ptg.
668
        $ptgArea = pack('C', $this->ptg['ptgArea3d']);
669
670
        return $ptgArea . $ext_ref . $row1 . $row2 . $col1 . $col2;
671
    }
672
673
    /**
674
     * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
675
     *
676
     * @param string $cell An Excel cell reference
677
     * @return string The cell in packed() format with the corresponding ptg
678
     */
679
    private function convertRef2d($cell)
680
    {
681
        // Convert the cell reference
682
        $cell_array = $this->cellToPackedRowcol($cell);
683
        list($row, $col) = $cell_array;
684
685
        // The ptg value depends on the class of the ptg.
686
        $ptgRef = pack('C', $this->ptg['ptgRefA']);
687
688
        return $ptgRef . $row . $col;
689
    }
690
691
    /**
692
     * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
693
     * ptgRef3d.
694
     *
695
     * @param string $cell An Excel cell reference
696
     * @return mixed The packed ptgRef3d token on success.
697
     */
698
    private function convertRef3d($cell)
699
    {
700
        // Split the ref at the ! symbol
701
        list($ext_ref, $cell) = explode('!', $cell);
702
703
        // Convert the external reference part (different for BIFF8)
704
        $ext_ref = $this->getRefIndex($ext_ref);
705
706
        // Convert the cell reference part
707
        list($row, $col) = $this->cellToPackedRowcol($cell);
708
709
        // The ptg value depends on the class of the ptg.
710
        $ptgRef = pack('C', $this->ptg['ptgRef3dA']);
711
712
        return $ptgRef . $ext_ref . $row . $col;
713
    }
714
715
    /**
716
     * Convert an error code to a ptgErr
717
     *
718
     * @param    string    $errorCode    The error code for conversion to its ptg value
719
     * @return    string                The error code ptgErr
720
     */
721
    private function convertError($errorCode)
722
    {
723
        switch ($errorCode) {
724
            case '#NULL!':
725
                return pack('C', 0x00);
726
            case '#DIV/0!':
727
                return pack('C', 0x07);
728
            case '#VALUE!':
729
                return pack('C', 0x0F);
730
            case '#REF!':
731
                return pack('C', 0x17);
732
            case '#NAME?':
733
                return pack('C', 0x1D);
734
            case '#NUM!':
735
                return pack('C', 0x24);
736
            case '#N/A':
737
                return pack('C', 0x2A);
738
        }
739
740
        return pack('C', 0xFF);
741
    }
742
743
    /**
744
     * Convert the sheet name part of an external reference, for example "Sheet1" or
745
     * "Sheet1:Sheet2", to a packed structure.
746
     *
747
     * @param    string    $ext_ref    The name of the external reference
748
     * @return    string                The reference index in packed() format
749
     */
750
    private function packExtRef($ext_ref)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
751
    {
752
        $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
753
        $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
754
755
        // Check if there is a sheet range eg., Sheet1:Sheet2.
756 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...
757
            list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
758
759
            $sheet1 = $this->getSheetIndex($sheet_name1);
760
            if ($sheet1 == -1) {
761
                throw new \PhpSpreadsheet\Writer\Exception("Unknown sheet name $sheet_name1 in formula");
762
            }
763
            $sheet2 = $this->getSheetIndex($sheet_name2);
764
            if ($sheet2 == -1) {
765
                throw new \PhpSpreadsheet\Writer\Exception("Unknown sheet name $sheet_name2 in formula");
766
            }
767
768
            // Reverse max and min sheet numbers if necessary
769
            if ($sheet1 > $sheet2) {
770
                list($sheet1, $sheet2) = [$sheet2, $sheet1];
771
            }
772
        } else { // Single sheet name only.
773
            $sheet1 = $this->getSheetIndex($ext_ref);
774
            if ($sheet1 == -1) {
775
                throw new \PhpSpreadsheet\Writer\Exception("Unknown sheet name $ext_ref in formula");
776
            }
777
            $sheet2 = $sheet1;
778
        }
779
780
        // References are stored relative to 0xFFFF.
781
        $offset = -1 - $sheet1;
782
783
        return pack('vdvv', $offset, 0x00, $sheet1, $sheet2);
784
    }
785
786
    /**
787
     * Look up the REF index that corresponds to an external sheet name
788
     * (or range). If it doesn't exist yet add it to the workbook's references
789
     * array. It assumes all sheet names given must exist.
790
     *
791
     * @param string $ext_ref The name of the external reference
792
     * @return mixed The reference index in packed() format on success
793
     */
794
    private function getRefIndex($ext_ref)
795
    {
796
        $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
797
        $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
798
        $ext_ref = str_replace('\'\'', '\'', $ext_ref); // Replace escaped '' with '
799
800
        // Check if there is a sheet range eg., Sheet1:Sheet2.
801 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...
802
            list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
803
804
            $sheet1 = $this->getSheetIndex($sheet_name1);
805
            if ($sheet1 == -1) {
806
                throw new \PhpSpreadsheet\Writer\Exception("Unknown sheet name $sheet_name1 in formula");
807
            }
808
            $sheet2 = $this->getSheetIndex($sheet_name2);
809
            if ($sheet2 == -1) {
810
                throw new \PhpSpreadsheet\Writer\Exception("Unknown sheet name $sheet_name2 in formula");
811
            }
812
813
            // Reverse max and min sheet numbers if necessary
814
            if ($sheet1 > $sheet2) {
815
                list($sheet1, $sheet2) = [$sheet2, $sheet1];
816
            }
817
        } else { // Single sheet name only.
818
            $sheet1 = $this->getSheetIndex($ext_ref);
819
            if ($sheet1 == -1) {
820
                throw new \PhpSpreadsheet\Writer\Exception("Unknown sheet name $ext_ref in formula");
821
            }
822
            $sheet2 = $sheet1;
823
        }
824
825
        // assume all references belong to this document
826
        $supbook_index = 0x00;
827
        $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
828
        $totalreferences = count($this->references);
829
        $index = -1;
830
        for ($i = 0; $i < $totalreferences; ++$i) {
831
            if ($ref == $this->references[$i]) {
832
                $index = $i;
833
                break;
834
            }
835
        }
836
        // if REF was not found add it to references array
837
        if ($index == -1) {
838
            $this->references[$totalreferences] = $ref;
839
            $index = $totalreferences;
840
        }
841
842
        return pack('v', $index);
843
    }
844
845
    /**
846
     * Look up the index that corresponds to an external sheet name. The hash of
847
     * sheet names is updated by the addworksheet() method of the
848
     * \PhpSpreadsheet\Writer\Excel5\Workbook class.
849
     *
850
     * @param    string    $sheet_name        Sheet name
851
     * @return    int                    The sheet index, -1 if the sheet was not found
852
     */
853
    private function getSheetIndex($sheet_name)
854
    {
855
        if (!isset($this->externalSheets[$sheet_name])) {
856
            return -1;
857
        } else {
858
            return $this->externalSheets[$sheet_name];
859
        }
860
    }
861
862
    /**
863
     * This method is used to update the array of sheet names. It is
864
     * called by the addWorksheet() method of the
865
     * \PhpSpreadsheet\Writer\Excel5\Workbook class.
866
     *
867
     * @see \PhpSpreadsheet\Writer\Excel5\Workbook::addWorksheet()
868
     * @param string  $name  The name of the worksheet being added
869
     * @param int $index The index of the worksheet being added
870
     */
871
    public function setExtSheet($name, $index)
872
    {
873
        $this->externalSheets[$name] = $index;
874
    }
875
876
    /**
877
     * pack() row and column into the required 3 or 4 byte format.
878
     *
879
     * @param string $cell The Excel cell reference to be packed
880
     * @return array Array containing the row and column in packed() format
881
     */
882
    private function cellToPackedRowcol($cell)
883
    {
884
        $cell = strtoupper($cell);
885
        list($row, $col, $row_rel, $col_rel) = $this->cellToRowcol($cell);
886
        if ($col >= 256) {
887
            throw new \PhpSpreadsheet\Writer\Exception("Column in: $cell greater than 255");
888
        }
889
        if ($row >= 65536) {
890
            throw new \PhpSpreadsheet\Writer\Exception("Row in: $cell greater than 65536 ");
891
        }
892
893
        // Set the high bits to indicate if row or col are relative.
894
        $col |= $col_rel << 14;
895
        $col |= $row_rel << 15;
896
        $col = pack('v', $col);
897
898
        $row = pack('v', $row);
899
900
        return [$row, $col];
901
    }
902
903
    /**
904
     * pack() row range into the required 3 or 4 byte format.
905
     * Just using maximum col/rows, which is probably not the correct solution
906
     *
907
     * @param string $range The Excel range to be packed
908
     * @return array Array containing (row1,col1,row2,col2) in packed() format
909
     */
910
    private function rangeToPackedRange($range)
911
    {
912
        preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match);
913
        // return absolute rows if there is a $ in the ref
914
        $row1_rel = empty($match[1]) ? 1 : 0;
915
        $row1 = $match[2];
916
        $row2_rel = empty($match[3]) ? 1 : 0;
917
        $row2 = $match[4];
918
        // Convert 1-index to zero-index
919
        --$row1;
920
        --$row2;
921
        // Trick poor inocent Excel
922
        $col1 = 0;
923
        $col2 = 65535; // FIXME: maximum possible value for Excel 5 (change this!!!)
924
925
        // FIXME: this changes for BIFF8
926
        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...
927
            throw new \PhpSpreadsheet\Writer\Exception("Row in: $range greater than 65536 ");
928
        }
929
930
        // Set the high bits to indicate if rows are relative.
931
        $col1 |= $row1_rel << 15;
932
        $col2 |= $row2_rel << 15;
933
        $col1 = pack('v', $col1);
934
        $col2 = pack('v', $col2);
935
936
        $row1 = pack('v', $row1);
937
        $row2 = pack('v', $row2);
938
939
        return [$row1, $col1, $row2, $col2];
940
    }
941
942
    /**
943
     * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
944
     * indexed row and column number. Also returns two (0,1) values to indicate
945
     * whether the row or column are relative references.
946
     *
947
     * @param string $cell The Excel cell reference in A1 format.
948
     * @return array
949
     */
950
    private function cellToRowcol($cell)
951
    {
952
        preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/', $cell, $match);
953
        // return absolute column if there is a $ in the ref
954
        $col_rel = empty($match[1]) ? 1 : 0;
955
        $col_ref = $match[2];
956
        $row_rel = empty($match[3]) ? 1 : 0;
957
        $row = $match[4];
958
959
        // Convert base26 column string to a number.
960
        $expn = strlen($col_ref) - 1;
961
        $col = 0;
962
        $col_ref_length = strlen($col_ref);
963
        for ($i = 0; $i < $col_ref_length; ++$i) {
964
            $col += (ord($col_ref{$i}) - 64) * pow(26, $expn);
965
            --$expn;
966
        }
967
968
        // Convert 1-index to zero-index
969
        --$row;
970
        --$col;
971
972
        return [$row, $col, $row_rel, $col_rel];
973
    }
974
975
    /**
976
     * Advance to the next valid token.
977
     */
978
    private function advance()
979
    {
980
        $i = $this->currentCharacter;
981
        $formula_length = strlen($this->formula);
982
        // eat up white spaces
983
        if ($i < $formula_length) {
984
            while ($this->formula{$i} == ' ') {
985
                ++$i;
986
            }
987
988
            if ($i < ($formula_length - 1)) {
989
                $this->lookAhead = $this->formula{$i + 1};
990
            }
991
            $token = '';
992
        }
993
994
        while ($i < $formula_length) {
995
            $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...
996
997 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...
998
                $this->lookAhead = $this->formula{$i + 1};
999
            } else {
1000
                $this->lookAhead = '';
1001
            }
1002
1003
            if ($this->match($token) != '') {
1004
                $this->currentCharacter = $i + 1;
1005
                $this->currentToken = $token;
1006
1007
                return 1;
1008
            }
1009
1010 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...
1011
                $this->lookAhead = $this->formula{$i + 2};
1012
            } else { // if we run out of characters lookAhead becomes empty
1013
                $this->lookAhead = '';
1014
            }
1015
            ++$i;
1016
        }
1017
        //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...
1018
    }
1019
1020
    /**
1021
     * Checks if it's a valid token.
1022
     *
1023
     * @param mixed $token The token to check.
1024
     * @return mixed       The checked token or false on failure
1025
     */
1026
    private function match($token)
1027
    {
1028
        switch ($token) {
1029
            case '+':
1030
            case '-':
1031
            case '*':
1032
            case '/':
1033
            case '(':
1034
            case ')':
1035
            case ',':
1036
            case ';':
1037
            case '>=':
1038
            case '<=':
1039
            case '=':
1040
            case '<>':
1041
            case '^':
1042
            case '&':
1043
            case '%':
1044
                return $token;
1045
                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...
1046
            case '>':
1047
                if ($this->lookAhead == '=') { // it's a GE token
1048
                    break;
1049
                }
1050
1051
                return $token;
1052
                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...
1053
            case '<':
1054
                // it's a LE or a NE token
1055
                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...
1056
                    break;
1057
                }
1058
1059
                return $token;
1060
                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...
1061
            default:
1062
                // if it's a reference A1 or $A$1 or $A1 or A$1
1063
                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...
1064
                    return $token;
1065
                } 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...
1066
                    // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
1067
                    return $token;
1068
                } 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...
1069
                    // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
1070
                    return $token;
1071
                } 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)) {
1072
                    // if it's a range A1:A2 or $A$1:$A$2
1073
                    return $token;
1074
                } 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...
1075
                    // 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
1076
                    return $token;
1077
                } 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...
1078
                    // 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
1079
                    return $token;
1080
                } 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...
1081
                    // If it's a number (check that it's not a sheet name or range)
1082
                    return $token;
1083
                } 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...
1084
                    // If it's a string (of maximum 255 characters)
1085
                    return $token;
1086
                } 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...
1087
                    // If it's an error code
1088
                    return $token;
1089
                } 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...
1090
                    // if it's a function call
1091
                    return $token;
1092
                } elseif (substr($token, -1) == ')') {
1093
                    //    It's an argument of some description (e.g. a named range),
1094
                    //        precise nature yet to be determined
1095
                    return $token;
1096
                }
1097
1098
                return '';
1099
        }
1100
    }
1101
1102
    /**
1103
     * The parsing method. It parses a formula.
1104
     *
1105
     * @param string $formula The formula to parse, without the initial equal
1106
     *                        sign (=).
1107
     * @return mixed true on success
1108
     */
1109
    public function parse($formula)
1110
    {
1111
        $this->currentCharacter = 0;
1112
        $this->formula = $formula;
1113
        $this->lookAhead = isset($formula{1}) ? $formula{1}
1114
        : '';
1115
        $this->advance();
1116
        $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...
1117
1118
        return true;
1119
    }
1120
1121
    /**
1122
     * It parses a condition. It assumes the following rule:
1123
     * Cond -> Expr [(">" | "<") Expr]
1124
     *
1125
     * @return mixed The parsed ptg'd tree on success
1126
     */
1127
    private function condition()
1128
    {
1129
        $result = $this->expression();
1130
        if ($this->currentToken == '<') {
1131
            $this->advance();
1132
            $result2 = $this->expression();
1133
            $result = $this->createTree('ptgLT', $result, $result2);
1134 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...
1135
            $this->advance();
1136
            $result2 = $this->expression();
1137
            $result = $this->createTree('ptgGT', $result, $result2);
1138
        } elseif ($this->currentToken == '<=') {
1139
            $this->advance();
1140
            $result2 = $this->expression();
1141
            $result = $this->createTree('ptgLE', $result, $result2);
1142 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...
1143
            $this->advance();
1144
            $result2 = $this->expression();
1145
            $result = $this->createTree('ptgGE', $result, $result2);
1146
        } elseif ($this->currentToken == '=') {
1147
            $this->advance();
1148
            $result2 = $this->expression();
1149
            $result = $this->createTree('ptgEQ', $result, $result2);
1150 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...
1151
            $this->advance();
1152
            $result2 = $this->expression();
1153
            $result = $this->createTree('ptgNE', $result, $result2);
1154
        } elseif ($this->currentToken == '&') {
1155
            $this->advance();
1156
            $result2 = $this->expression();
1157
            $result = $this->createTree('ptgConcat', $result, $result2);
1158
        }
1159
1160
        return $result;
1161
    }
1162
1163
    /**
1164
     * It parses a expression. It assumes the following rule:
1165
     * Expr -> Term [("+" | "-") Term]
1166
     *      -> "string"
1167
     *      -> "-" Term : Negative value
1168
     *      -> "+" Term : Positive value
1169
     *      -> Error code
1170
     *
1171
     * @return mixed The parsed ptg'd tree on success
1172
     */
1173
    private function expression()
1174
    {
1175
        // If it's a string return a string node
1176
        if (preg_match('/"([^"]|""){0,255}"/', $this->currentToken)) {
1177
            $tmp = str_replace('""', '"', $this->currentToken);
1178
            if (($tmp == '"') || ($tmp == '')) {
1179
                //    Trap for "" that has been used for an empty string
1180
                $tmp = '""';
1181
            }
1182
            $result = $this->createTree($tmp, '', '');
1183
            $this->advance();
1184
1185
            return $result;
1186
        // If it's an error code
1187
        } 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...
1188
            $result = $this->createTree($this->currentToken, 'ptgErr', '');
1189
            $this->advance();
1190
1191
            return $result;
1192
        // If it's a negative value
1193
        } elseif ($this->currentToken == '-') {
1194
            // catch "-" Term
1195
            $this->advance();
1196
            $result2 = $this->expression();
1197
            $result = $this->createTree('ptgUminus', $result2, '');
1198
1199
            return $result;
1200
        // If it's a positive value
1201 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...
1202
            // catch "+" Term
1203
            $this->advance();
1204
            $result2 = $this->expression();
1205
            $result = $this->createTree('ptgUplus', $result2, '');
1206
1207
            return $result;
1208
        }
1209
        $result = $this->term();
1210
        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...
1211
               ($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...
1212
               ($this->currentToken == '^')) {
1213
            /**/
1214
            if ($this->currentToken == '+') {
1215
                $this->advance();
1216
                $result2 = $this->term();
1217
                $result = $this->createTree('ptgAdd', $result, $result2);
1218
            } elseif ($this->currentToken == '-') {
1219
                $this->advance();
1220
                $result2 = $this->term();
1221
                $result = $this->createTree('ptgSub', $result, $result2);
1222
            } else {
1223
                $this->advance();
1224
                $result2 = $this->term();
1225
                $result = $this->createTree('ptgPower', $result, $result2);
1226
            }
1227
        }
1228
1229
        return $result;
1230
    }
1231
1232
    /**
1233
     * This function just introduces a ptgParen element in the tree, so that Excel
1234
     * doesn't get confused when working with a parenthesized formula afterwards.
1235
     *
1236
     * @see fact()
1237
     * @return array The parsed ptg'd tree
1238
     */
1239
    private function parenthesizedExpression()
1240
    {
1241
        $result = $this->createTree('ptgParen', $this->expression(), '');
1242
1243
        return $result;
1244
    }
1245
1246
    /**
1247
     * It parses a term. It assumes the following rule:
1248
     * Term -> Fact [("*" | "/") Fact]
1249
     *
1250
     * @return mixed The parsed ptg'd tree on success
1251
     */
1252
    private function term()
1253
    {
1254
        $result = $this->fact();
1255
        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...
1256
               ($this->currentToken == '/')) {
1257
            /**/
1258
            if ($this->currentToken == '*') {
1259
                $this->advance();
1260
                $result2 = $this->fact();
1261
                $result = $this->createTree('ptgMul', $result, $result2);
1262
            } else {
1263
                $this->advance();
1264
                $result2 = $this->fact();
1265
                $result = $this->createTree('ptgDiv', $result, $result2);
1266
            }
1267
        }
1268
1269
        return $result;
1270
    }
1271
1272
    /**
1273
     * It parses a factor. It assumes the following rule:
1274
     * Fact -> ( Expr )
1275
     *       | CellRef
1276
     *       | CellRange
1277
     *       | Number
1278
     *       | Function
1279
     *
1280
     * @return mixed The parsed ptg'd tree on success
1281
     */
1282
    private function fact()
1283
    {
1284
        if ($this->currentToken == '(') {
1285
            $this->advance(); // eat the "("
1286
            $result = $this->parenthesizedExpression();
1287
            if ($this->currentToken != ')') {
1288
                throw new \PhpSpreadsheet\Writer\Exception("')' token expected.");
1289
            }
1290
            $this->advance(); // eat the ")"
1291
            return $result;
1292
        }
1293
        // if it's a reference
1294
        if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/', $this->currentToken)) {
1295
            $result = $this->createTree($this->currentToken, '', '');
1296
            $this->advance();
1297
1298
            return $result;
1299 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...
1300
            // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
1301
            $result = $this->createTree($this->currentToken, '', '');
1302
            $this->advance();
1303
1304
            return $result;
1305
        } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?[A-Ia-i]?[A-Za-z]\\$?[0-9]+$/u", $this->currentToken)) {
1306
            // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
1307
            $result = $this->createTree($this->currentToken, '', '');
1308
            $this->advance();
1309
1310
            return $result;
1311
        } 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...
1312
                preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/', $this->currentToken)) {
1313
            // if it's a range A1:B2 or $A$1:$B$2
1314
            // must be an error?
1315
            $result = $this->createTree($this->currentToken, '', '');
1316
            $this->advance();
1317
1318
            return $result;
1319 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...
1320
            // 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)
1321
            // must be an error?
1322
            $result = $this->createTree($this->currentToken, '', '');
1323
            $this->advance();
1324
1325
            return $result;
1326
        } 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)) {
1327
            // 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)
1328
            // must be an error?
1329
            $result = $this->createTree($this->currentToken, '', '');
1330
            $this->advance();
1331
1332
            return $result;
1333
        } elseif (is_numeric($this->currentToken)) {
1334
            // If it's a number or a percent
1335
            if ($this->lookAhead == '%') {
1336
                $result = $this->createTree('ptgPercent', $this->currentToken, '');
1337
                $this->advance(); // Skip the percentage operator once we've pre-built that tree
1338
            } else {
1339
                $result = $this->createTree($this->currentToken, '', '');
1340
            }
1341
            $this->advance();
1342
1343
            return $result;
1344
        } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i", $this->currentToken)) {
1345
            // if it's a function call
1346
            $result = $this->func();
1347
1348
            return $result;
1349
        }
1350
        throw new \PhpSpreadsheet\Writer\Exception('Syntax error: ' . $this->currentToken . ', lookahead: ' . $this->lookAhead . ', current char: ' . $this->currentCharacter);
1351
    }
1352
1353
    /**
1354
     * It parses a function call. It assumes the following rule:
1355
     * Func -> ( Expr [,Expr]* )
1356
     *
1357
     * @return mixed The parsed ptg'd tree on success
1358
     */
1359
    private function func()
1360
    {
1361
        $num_args = 0; // number of arguments received
1362
        $function = strtoupper($this->currentToken);
1363
        $result = ''; // initialize result
1364
        $this->advance();
1365
        $this->advance(); // eat the "("
1366
        while ($this->currentToken != ')') {
1367
            /**/
1368
            if ($num_args > 0) {
1369
                if ($this->currentToken == ',' || $this->currentToken == ';') {
1370
                    $this->advance(); // eat the "," or ";"
1371
                } else {
1372
                    throw new \PhpSpreadsheet\Writer\Exception("Syntax error: comma expected in function $function, arg #{$num_args}");
1373
                }
1374
                $result2 = $this->condition();
1375
                $result = $this->createTree('arg', $result, $result2);
1376
            } else { // first argument
1377
                $result2 = $this->condition();
1378
                $result = $this->createTree('arg', '', $result2);
1379
            }
1380
            ++$num_args;
1381
        }
1382
        if (!isset($this->functions[$function])) {
1383
            throw new \PhpSpreadsheet\Writer\Exception("Function $function() doesn't exist");
1384
        }
1385
        $args = $this->functions[$function][1];
1386
        // If fixed number of args eg. TIME($i, $j, $k). Check that the number of args is valid.
1387
        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...
1388
            throw new \PhpSpreadsheet\Writer\Exception("Incorrect number of arguments in function $function() ");
1389
        }
1390
1391
        $result = $this->createTree($function, $result, $num_args);
1392
        $this->advance(); // eat the ")"
1393
        return $result;
1394
    }
1395
1396
    /**
1397
     * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
1398
     * as elements.
1399
     *
1400
     * @param mixed $value The value of this node.
1401
     * @param mixed $left  The left array (sub-tree) or a final node.
1402
     * @param mixed $right The right array (sub-tree) or a final node.
1403
     * @return array A tree
1404
     */
1405
    private function createTree($value, $left, $right)
1406
    {
1407
        return ['value' => $value, 'left' => $left, 'right' => $right];
1408
    }
1409
1410
    /**
1411
     * Builds a string containing the tree in reverse polish notation (What you
1412
     * would use in a HP calculator stack).
1413
     * The following tree:
1414
     *
1415
     *    +
1416
     *   / \
1417
     *  2   3
1418
     *
1419
     * produces: "23+"
1420
     *
1421
     * The following tree:
1422
     *
1423
     *    +
1424
     *   / \
1425
     *  3   *
1426
     *     / \
1427
     *    6   A1
1428
     *
1429
     * produces: "36A1*+"
1430
     *
1431
     * In fact all operands, functions, references, etc... are written as ptg's
1432
     *
1433
     * @param array $tree The optional tree to convert.
1434
     * @return string The tree in reverse polish notation
1435
     */
1436
    public function toReversePolish($tree = [])
1437
    {
1438
        $polish = ''; // the string we are going to return
1439
        if (empty($tree)) { // If it's the first call use parseTree
1440
            $tree = $this->parseTree;
1441
        }
1442
1443 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...
1444
            $converted_tree = $this->toReversePolish($tree['left']);
1445
            $polish .= $converted_tree;
1446
        } elseif ($tree['left'] != '') { // It's a final node
1447
            $converted_tree = $this->convert($tree['left']);
1448
            $polish .= $converted_tree;
1449
        }
1450 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...
1451
            $converted_tree = $this->toReversePolish($tree['right']);
1452
            $polish .= $converted_tree;
1453
        } elseif ($tree['right'] != '') { // It's a final node
1454
            $converted_tree = $this->convert($tree['right']);
1455
            $polish .= $converted_tree;
1456
        }
1457
        // if it's a function convert it here (so we can set it's arguments)
1458
        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...
1459
            !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...
1460
            !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...
1461
            !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...
1462
            !isset($this->ptg[$tree['value']])) {
1463
            // left subtree for a function is always an array.
1464
            if ($tree['left'] != '') {
1465
                $left_tree = $this->toReversePolish($tree['left']);
1466
            } else {
1467
                $left_tree = '';
1468
            }
1469
            // add it's left subtree and return.
1470
            return $left_tree . $this->convertFunction($tree['value'], $tree['right']);
1471
        } else {
1472
            $converted_tree = $this->convert($tree['value']);
1473
        }
1474
        $polish .= $converted_tree;
1475
1476
        return $polish;
1477
    }
1478
}
1479