Completed
Push — develop ( 39b55d...e2e982 )
by Adrien
19:14
created

Parser::convertRange2d()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 6.9683

Importance

Changes 0
Metric Value
cc 5
eloc 16
nc 5
nop 2
dl 0
loc 30
ccs 8
cts 14
cp 0.5714
crap 6.9683
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
4
5
/**
6
 * Copyright (c) 2006 - 2015 PhpSpreadsheet
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 *
22
 * @category   PhpSpreadsheet
23
 * @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 38
    public function __construct()
113
    {
114 38
        $this->currentCharacter = 0;
115 38
        $this->currentToken = ''; // The token we are working on.
116 38
        $this->formula = ''; // The formula to parse.
117 38
        $this->lookAhead = ''; // The character ahead of the current char.
118 38
        $this->parseTree = ''; // The parse tree to be generated.
119 38
        $this->initializeHashes(); // Initialize the hashes: ptg's and function's ptg's
120 38
        $this->externalSheets = [];
121 38
        $this->references = [];
122 38
    }
123
124
    /**
125
     * Initialize the ptg and function hashes.
126
     */
127 38
    private function initializeHashes()
128
    {
129
        // The Excel ptg indices
130 38
        $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
            'ptgMemNoMemNV' => 0x4F,
198
            'ptgFuncCEV' => 0x58,
199
            'ptgNameXV' => 0x59,
200
            'ptgRef3dV' => 0x5A,
201
            'ptgArea3dV' => 0x5B,
202
            'ptgRefErr3dV' => 0x5C,
203
            'ptgAreaErr3dV' => 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
            'ptgMemNoMemNA' => 0x6F,
220
            'ptgFuncCEA' => 0x78,
221
            'ptgNameXA' => 0x79,
222
            'ptgRef3dA' => 0x7A,
223
            'ptgArea3dA' => 0x7B,
224
            'ptgRefErr3dA' => 0x7C,
225
            'ptgAreaErr3dA' => 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 38
        $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 38
    }
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 18
    private function convert($token)
503
    {
504 18
        if (preg_match('/"([^"]|""){0,255}"/', $token)) {
505 9
            return $this->convertString($token);
506 18
        } elseif (is_numeric($token)) {
507 15
            return $this->convertNumber($token);
508
        // match references like A1 or $A$1
509 18
        } elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/', $token)) {
510 12
            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 18
        } 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 18
        } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?[A-Ia-i]?[A-Za-z]\\$?(\d+)$/u", $token)) {
516 1
            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 18
        } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/', $token)) {
519 15
            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 17
        } 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 17
        } 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 3
            return $this->convertRange3d($token);
526
        // operators (including parentheses)
527 16
        } elseif (isset($this->ptg[$token])) {
528 12
            return pack('C', $this->ptg[$token]);
529
        // match error codes
530 15
        } 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 15
        } elseif ($token == 'arg') {
539 15
            return '';
540
        }
541
542
        // TODO: use real error codes
543
        throw new \PhpOffice\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 15
    private function convertNumber($num)
552
    {
553
        // Integer in the range 0..2**16-1
554 15
        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 15
            return pack('Cv', $this->ptg['ptgInt'], $num);
556
        } else { // A float
557 6
            if (BIFFwriter::getByteOrder()) { // if it's Big Endian
558
                $num = strrev($num);
559
            }
560
561 6
            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 9
    private function convertString($string)
572
    {
573
        // chop away beggining and ending quotes
574 9
        $string = substr($string, 1, strlen($string) - 2);
575 9
        if (strlen($string) > 255) {
576
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception('String is too long');
577
        }
578
579 9
        return pack('C', $this->ptg['ptgStr']) . \PhpOffice\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 15
    private function convertFunction($token, $num_args)
591
    {
592 15
        $args = $this->functions[$token][1];
593
594
        // Fixed number of args eg. TIME($i, $j, $k).
595 15
        if ($args >= 0) {
596 4
            return pack('Cv', $this->ptg['ptgFuncV'], $this->functions[$token][0]);
597
        }
598
        // Variable number of args eg. SUM($i, $j, $k, ..).
599 15
        if ($args == -1) {
600 15
            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 15
    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 15
        if (preg_match('/^(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)\:(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)$/', $range)) {
616 15
            list($cell1, $cell2) = explode(':', $range);
617
        } else {
618
            // TODO: use real error codes
619
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception('Unknown range separator');
620
        }
621
622
        // Convert the cell references
623 15
        list($row1, $col1) = $this->cellToPackedRowcol($cell1);
624 15
        list($row2, $col2) = $this->cellToPackedRowcol($cell2);
625
626
        // The ptg value depends on the class of the ptg.
627 15
        if ($class == 0) {
628 15
            $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 \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown class $class");
636
        }
637
638 15
        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 3
    private function convertRange3d($token)
649
    {
650
        // Split the ref at the ! symbol
651 3
        list($ext_ref, $range) = explode('!', $token);
652
653
        // Convert the external reference part (different for BIFF8)
654 3
        $ext_ref = $this->getRefIndex($ext_ref);
655
656
        // Split the range into 2 cell refs
657 3
        list($cell1, $cell2) = explode(':', $range);
658
659
        // Convert the cell references
660 3
        if (preg_match("/^(\\$)?[A-Ia-i]?[A-Za-z](\\$)?(\d+)$/", $cell1)) {
661 3
            list($row1, $col1) = $this->cellToPackedRowcol($cell1);
662 3
            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 3
        $ptgArea = pack('C', $this->ptg['ptgArea3d']);
669
670 3
        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 12
    private function convertRef2d($cell)
680
    {
681
        // Convert the cell reference
682 12
        $cell_array = $this->cellToPackedRowcol($cell);
683 12
        list($row, $col) = $cell_array;
684
685
        // The ptg value depends on the class of the ptg.
686 12
        $ptgRef = pack('C', $this->ptg['ptgRefA']);
687
688 12
        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 1
    private function convertRef3d($cell)
699
    {
700
        // Split the ref at the ! symbol
701 1
        list($ext_ref, $cell) = explode('!', $cell);
702
703
        // Convert the external reference part (different for BIFF8)
704 1
        $ext_ref = $this->getRefIndex($ext_ref);
705
706
        // Convert the cell reference part
707 1
        list($row, $col) = $this->cellToPackedRowcol($cell);
708
709
        // The ptg value depends on the class of the ptg.
710 1
        $ptgRef = pack('C', $this->ptg['ptgRef3dA']);
711
712 1
        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 \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown sheet name $sheet_name1 in formula");
762
            }
763
            $sheet2 = $this->getSheetIndex($sheet_name2);
764
            if ($sheet2 == -1) {
765
                throw new \PhpOffice\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 \PhpOffice\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 4
    private function getRefIndex($ext_ref)
795
    {
796 4
        $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
797 4
        $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
798 4
        $ext_ref = str_replace('\'\'', '\'', $ext_ref); // Replace escaped '' with '
799
800
        // Check if there is a sheet range eg., Sheet1:Sheet2.
801 4 View Code Duplication
        if (preg_match('/:/', $ext_ref)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
802
            list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
803
804
            $sheet1 = $this->getSheetIndex($sheet_name1);
805
            if ($sheet1 == -1) {
806
                throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown sheet name $sheet_name1 in formula");
807
            }
808
            $sheet2 = $this->getSheetIndex($sheet_name2);
809
            if ($sheet2 == -1) {
810
                throw new \PhpOffice\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 4
            $sheet1 = $this->getSheetIndex($ext_ref);
819 4
            if ($sheet1 == -1) {
820
                throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Unknown sheet name $ext_ref in formula");
821
            }
822 4
            $sheet2 = $sheet1;
823
        }
824
825
        // assume all references belong to this document
826 4
        $supbook_index = 0x00;
827 4
        $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
828 4
        $totalreferences = count($this->references);
829 4
        $index = -1;
830 4
        for ($i = 0; $i < $totalreferences; ++$i) {
831 4
            if ($ref == $this->references[$i]) {
832 4
                $index = $i;
833 4
                break;
834
            }
835
        }
836
        // if REF was not found add it to references array
837 4
        if ($index == -1) {
838
            $this->references[$totalreferences] = $ref;
839
            $index = $totalreferences;
840
        }
841
842 4
        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
     * \PhpOffice\PhpSpreadsheet\Writer\Xls\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 4
    private function getSheetIndex($sheet_name)
854
    {
855 4
        if (!isset($this->externalSheets[$sheet_name])) {
856
            return -1;
857
        } else {
858 4
            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
     * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class.
866
     *
867
     * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\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 38
    public function setExtSheet($name, $index)
872
    {
873 38
        $this->externalSheets[$name] = $index;
874 38
    }
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 17
    private function cellToPackedRowcol($cell)
883
    {
884 17
        $cell = strtoupper($cell);
885 17
        list($row, $col, $row_rel, $col_rel) = $this->cellToRowcol($cell);
886 17
        if ($col >= 256) {
887
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Column in: $cell greater than 255");
888
        }
889 17
        if ($row >= 65536) {
890
            throw new \PhpOffice\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 17
        $col |= $col_rel << 14;
895 17
        $col |= $row_rel << 15;
896 17
        $col = pack('v', $col);
897
898 17
        $row = pack('v', $row);
899
900 17
        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 \PhpOffice\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 17
    private function cellToRowcol($cell)
951
    {
952 17
        preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/', $cell, $match);
953
        // return absolute column if there is a $ in the ref
954 17
        $col_rel = empty($match[1]) ? 1 : 0;
955 17
        $col_ref = $match[2];
956 17
        $row_rel = empty($match[3]) ? 1 : 0;
957 17
        $row = $match[4];
958
959
        // Convert base26 column string to a number.
960 17
        $expn = strlen($col_ref) - 1;
961 17
        $col = 0;
962 17
        $col_ref_length = strlen($col_ref);
963 17
        for ($i = 0; $i < $col_ref_length; ++$i) {
964 17
            $col += (ord($col_ref{$i}) - 64) * pow(26, $expn);
965 17
            --$expn;
966
        }
967
968
        // Convert 1-index to zero-index
969 17
        --$row;
970 17
        --$col;
971
972 17
        return [$row, $col, $row_rel, $col_rel];
973
    }
974
975
    /**
976
     * Advance to the next valid token.
977
     */
978 18
    private function advance()
979
    {
980 18
        $i = $this->currentCharacter;
981 18
        $formula_length = strlen($this->formula);
982
        // eat up white spaces
983 18
        if ($i < $formula_length) {
984 18
            while ($this->formula{$i} == ' ') {
985 2
                ++$i;
986
            }
987
988 18
            if ($i < ($formula_length - 1)) {
989 18
                $this->lookAhead = $this->formula{$i + 1};
990
            }
991 18
            $token = '';
992
        }
993
994 18
        while ($i < $formula_length) {
995 18
            $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 18 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 18
                $this->lookAhead = $this->formula{$i + 1};
999
            } else {
1000 18
                $this->lookAhead = '';
1001
            }
1002
1003 18
            if ($this->match($token) != '') {
1004 18
                $this->currentCharacter = $i + 1;
1005 18
                $this->currentToken = $token;
1006
1007 18
                return 1;
1008
            }
1009
1010 18 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 18
                $this->lookAhead = $this->formula{$i + 2};
1012
            } else { // if we run out of characters lookAhead becomes empty
1013 14
                $this->lookAhead = '';
1014
            }
1015 18
            ++$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 18
    }
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 18
    private function match($token)
1027
    {
1028
        switch ($token) {
1029 18
            case '+':
1030 18
            case '-':
1031 18
            case '*':
1032 18
            case '/':
1033 18
            case '(':
1034 18
            case ')':
1035 18
            case ',':
1036 18
            case ';':
1037 18
            case '>=':
1038 18
            case '<=':
1039 18
            case '=':
1040 18
            case '<>':
1041 18
            case '^':
1042 18
            case '&':
1043 18
            case '%':
1044 17
                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 18
            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 18
            case '<':
1054
                // it's a LE or a NE token
1055 5
                if (($this->lookAhead == '=') or ($this->lookAhead == '>')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

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

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

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

Let’s take a look at a few examples:

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

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


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

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

Logical Operators are used for Control-Flow

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

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

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

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

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

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

Loading history...
1056 5
                    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 18
                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 12
                    return $token;
1065 18
                } 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 18
                } 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 1
                    return $token;
1071 18
                } 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 15
                    return $token;
1074 18
                } 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 18
                } 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 3
                    return $token;
1080 18
                } 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 10
                    return $token;
1083 18
                } 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 9
                    return $token;
1086 18
                } 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 18
                } 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 15
                    return $token;
1092 18
                } 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 3
                    return $token;
1096
                }
1097
1098 18
                return '';
1099
        }
1100 5
    }
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 18
    public function parse($formula)
1110
    {
1111 18
        $this->currentCharacter = 0;
1112 18
        $this->formula = $formula;
1113 18
        $this->lookAhead = isset($formula{1}) ? $formula{1}
1114 1
        : '';
1115 18
        $this->advance();
1116 18
        $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 18
        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 18
    private function condition()
1128
    {
1129 18
        $result = $this->expression();
1130 18
        if ($this->currentToken == '<') {
1131
            $this->advance();
1132
            $result2 = $this->expression();
1133
            $result = $this->createTree('ptgLT', $result, $result2);
1134 18 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 18
        } elseif ($this->currentToken == '<=') {
1139
            $this->advance();
1140
            $result2 = $this->expression();
1141
            $result = $this->createTree('ptgLE', $result, $result2);
1142 18 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 18
        } elseif ($this->currentToken == '=') {
1147
            $this->advance();
1148
            $result2 = $this->expression();
1149
            $result = $this->createTree('ptgEQ', $result, $result2);
1150 18 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 5
            $this->advance();
1152 5
            $result2 = $this->expression();
1153 5
            $result = $this->createTree('ptgNE', $result, $result2);
1154 18
        } elseif ($this->currentToken == '&') {
1155 6
            $this->advance();
1156 6
            $result2 = $this->expression();
1157 6
            $result = $this->createTree('ptgConcat', $result, $result2);
1158
        }
1159
1160 18
        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 18
    private function expression()
1174
    {
1175
        // If it's a string return a string node
1176 18
        if (preg_match('/"([^"]|""){0,255}"/', $this->currentToken)) {
1177 9
            $tmp = str_replace('""', '"', $this->currentToken);
1178 9
            if (($tmp == '"') || ($tmp == '')) {
1179
                //    Trap for "" that has been used for an empty string
1180 5
                $tmp = '""';
1181
            }
1182 9
            $result = $this->createTree($tmp, '', '');
1183 9
            $this->advance();
1184
1185 9
            return $result;
1186
        // If it's an error code
1187 18
        } 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 18
        } 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 18 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 18
        $result = $this->term();
1210 18
        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 18
               ($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 18
               ($this->currentToken == '^')) {
1213
            /**/
1214 10
            if ($this->currentToken == '+') {
1215 10
                $this->advance();
1216 10
                $result2 = $this->term();
1217 10
                $result = $this->createTree('ptgAdd', $result, $result2);
1218 3
            } elseif ($this->currentToken == '-') {
1219 3
                $this->advance();
1220 3
                $result2 = $this->term();
1221 3
                $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 18
        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 1
    private function parenthesizedExpression()
1240
    {
1241 1
        $result = $this->createTree('ptgParen', $this->expression(), '');
1242
1243 1
        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 18
    private function term()
1253
    {
1254 18
        $result = $this->fact();
1255 18
        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 18
               ($this->currentToken == '/')) {
1257
            /**/
1258 10
            if ($this->currentToken == '*') {
1259 10
                $this->advance();
1260 10
                $result2 = $this->fact();
1261 10
                $result = $this->createTree('ptgMul', $result, $result2);
1262
            } else {
1263 3
                $this->advance();
1264 3
                $result2 = $this->fact();
1265 3
                $result = $this->createTree('ptgDiv', $result, $result2);
1266
            }
1267
        }
1268
1269 18
        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 18
    private function fact()
1283
    {
1284 18
        if ($this->currentToken == '(') {
1285 1
            $this->advance(); // eat the "("
1286 1
            $result = $this->parenthesizedExpression();
1287 1
            if ($this->currentToken != ')') {
1288
                throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("')' token expected.");
1289
            }
1290 1
            $this->advance(); // eat the ")"
1291 1
            return $result;
1292
        }
1293
        // if it's a reference
1294 18
        if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/', $this->currentToken)) {
1295 12
            $result = $this->createTree($this->currentToken, '', '');
1296 12
            $this->advance();
1297
1298 12
            return $result;
1299 18 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 18
        } 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 1
            $result = $this->createTree($this->currentToken, '', '');
1308 1
            $this->advance();
1309
1310 1
            return $result;
1311 18
        } 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 18
                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 15
            $result = $this->createTree($this->currentToken, '', '');
1316 15
            $this->advance();
1317
1318 15
            return $result;
1319 18 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 18
        } 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 3
            $result = $this->createTree($this->currentToken, '', '');
1330 3
            $this->advance();
1331
1332 3
            return $result;
1333 18
        } elseif (is_numeric($this->currentToken)) {
1334
            // If it's a number or a percent
1335 10
            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 10
                $result = $this->createTree($this->currentToken, '', '');
1340
            }
1341 10
            $this->advance();
1342
1343 10
            return $result;
1344 18
        } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i", $this->currentToken)) {
1345
            // if it's a function call
1346 15
            $result = $this->func();
1347
1348 15
            return $result;
1349
        }
1350 6
        throw new \PhpOffice\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 15
    private function func()
1360
    {
1361 15
        $num_args = 0; // number of arguments received
1362 15
        $function = strtoupper($this->currentToken);
1363 15
        $result = ''; // initialize result
1364 15
        $this->advance();
1365 15
        $this->advance(); // eat the "("
1366 15
        while ($this->currentToken != ')') {
1367
            /**/
1368 15
            if ($num_args > 0) {
1369 7
                if ($this->currentToken == ',' || $this->currentToken == ';') {
1370 7
                    $this->advance(); // eat the "," or ";"
1371
                } else {
1372
                    throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Syntax error: comma expected in function $function, arg #{$num_args}");
1373
                }
1374 7
                $result2 = $this->condition();
1375 7
                $result = $this->createTree('arg', $result, $result2);
1376
            } else { // first argument
1377 15
                $result2 = $this->condition();
1378 15
                $result = $this->createTree('arg', '', $result2);
1379
            }
1380 15
            ++$num_args;
1381
        }
1382 15
        if (!isset($this->functions[$function])) {
1383 1
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Function $function() doesn't exist");
1384
        }
1385 15
        $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 15
        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 \PhpOffice\PhpSpreadsheet\Writer\Exception("Incorrect number of arguments in function $function() ");
1389
        }
1390
1391 15
        $result = $this->createTree($function, $result, $num_args);
1392 15
        $this->advance(); // eat the ")"
1393 15
        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 18
    private function createTree($value, $left, $right)
1406
    {
1407 18
        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 18
    public function toReversePolish($tree = [])
1437
    {
1438 18
        $polish = ''; // the string we are going to return
1439 18
        if (empty($tree)) { // If it's the first call use parseTree
1440 18
            $tree = $this->parseTree;
1441
        }
1442
1443 18 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 16
            $converted_tree = $this->toReversePolish($tree['left']);
1445 16
            $polish .= $converted_tree;
1446 18
        } elseif ($tree['left'] != '') { // It's a final node
1447
            $converted_tree = $this->convert($tree['left']);
1448
            $polish .= $converted_tree;
1449
        }
1450 18 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 16
            $converted_tree = $this->toReversePolish($tree['right']);
1452 16
            $polish .= $converted_tree;
1453 18
        } elseif ($tree['right'] != '') { // It's a final node
1454 15
            $converted_tree = $this->convert($tree['right']);
1455 15
            $polish .= $converted_tree;
1456
        }
1457
        // if it's a function convert it here (so we can set it's arguments)
1458 18
        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 18
            !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 18
            !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 18
            !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 18
            !isset($this->ptg[$tree['value']])) {
1463
            // left subtree for a function is always an array.
1464 15
            if ($tree['left'] != '') {
1465 15
                $left_tree = $this->toReversePolish($tree['left']);
1466
            } else {
1467 4
                $left_tree = '';
1468
            }
1469
            // add it's left subtree and return.
1470 15
            return $left_tree . $this->convertFunction($tree['value'], $tree['right']);
1471
        } else {
1472 18
            $converted_tree = $this->convert($tree['value']);
1473
        }
1474 18
        $polish .= $converted_tree;
1475
1476 18
        return $polish;
1477
    }
1478
}
1479