Passed
Push — master ( a97294...8972d3 )
by Adrien
28:45 queued 21:13
created

TextData::MID()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 10
nc 7
nop 3
dl 0
loc 19
ccs 11
cts 11
cp 1
crap 6
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation;
4
5
use PhpOffice\PhpSpreadsheet\Shared\Date;
6
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
7
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
8
9
class TextData
10
{
11
    private static $invalidChars;
12
13 15
    private static function unicodeToOrd($character)
14
    {
15 15
        return unpack('V', iconv('UTF-8', 'UCS-4LE', $character))[1];
16
    }
17
18
    /**
19
     * CHARACTER.
20
     *
21
     * @param string $character Value
22
     *
23
     * @return string
24
     */
25 11
    public static function CHARACTER($character)
26
    {
27 11
        $character = Functions::flattenSingleValue($character);
28
29 11
        if ((!is_numeric($character)) || ($character < 0)) {
30 2
            return Functions::VALUE();
31
        }
32
33 9
        if (function_exists('iconv')) {
34 9
            return iconv('UCS-4LE', 'UTF-8', pack('V', $character));
35
        }
36
37
        return mb_convert_encoding('&#' . (int) $character . ';', 'UTF-8', 'HTML-ENTITIES');
38
    }
39
40
    /**
41
     * TRIMNONPRINTABLE.
42
     *
43
     * @param mixed $stringValue Value to check
44
     *
45
     * @return string
46
     */
47 5
    public static function TRIMNONPRINTABLE($stringValue = '')
48
    {
49 5
        $stringValue = Functions::flattenSingleValue($stringValue);
50
51 5
        if (is_bool($stringValue)) {
52 1
            return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE();
53
        }
54
55 4
        if (self::$invalidChars === null) {
56 1
            self::$invalidChars = range(chr(0), chr(31));
57
        }
58
59 4
        if (is_string($stringValue) || is_numeric($stringValue)) {
60 3
            return str_replace(self::$invalidChars, '', trim($stringValue, "\x00..\x1F"));
61
        }
62
63 1
        return null;
64
    }
65
66
    /**
67
     * TRIMSPACES.
68
     *
69
     * @param mixed $stringValue Value to check
70
     *
71
     * @return string
72
     */
73 7
    public static function TRIMSPACES($stringValue = '')
74
    {
75 7
        $stringValue = Functions::flattenSingleValue($stringValue);
76 7
        if (is_bool($stringValue)) {
77 1
            return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE();
78
        }
79
80 6
        if (is_string($stringValue) || is_numeric($stringValue)) {
81 5
            return trim(preg_replace('/ +/', ' ', trim($stringValue, ' ')), ' ');
82
        }
83
84 1
        return null;
85
    }
86
87 6
    private static function convertBooleanValue($value)
88
    {
89 6
        if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
90 1
            return (int) $value;
91
        }
92
93 5
        return ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
94
    }
95
96
    /**
97
     * ASCIICODE.
98
     *
99
     * @param string $characters Value
100
     *
101
     * @return int
102
     */
103 17
    public static function ASCIICODE($characters)
104
    {
105 17
        if (($characters === null) || ($characters === '')) {
106 2
            return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type integer.
Loading history...
107
        }
108 15
        $characters = Functions::flattenSingleValue($characters);
109 15
        if (is_bool($characters)) {
110 1
            $characters = self::convertBooleanValue($characters);
111
        }
112
113 15
        $character = $characters;
114 15
        if (mb_strlen($characters, 'UTF-8') > 1) {
115 9
            $character = mb_substr($characters, 0, 1, 'UTF-8');
116
        }
117
118 15
        return self::unicodeToOrd($character);
119
    }
120
121
    /**
122
     * CONCATENATE.
123
     *
124
     * @return string
125
     */
126 4
    public static function CONCATENATE(...$args)
127
    {
128 4
        $returnValue = '';
129
130
        // Loop through arguments
131 4
        $aArgs = Functions::flattenArray($args);
132 4
        foreach ($aArgs as $arg) {
133 4
            if (is_bool($arg)) {
134 2
                $arg = self::convertBooleanValue($arg);
135
            }
136 4
            $returnValue .= $arg;
137
        }
138
139 4
        return $returnValue;
140
    }
141
142
    /**
143
     * DOLLAR.
144
     *
145
     * This function converts a number to text using currency format, with the decimals rounded to the specified place.
146
     * The format used is $#,##0.00_);($#,##0.00)..
147
     *
148
     * @param float $value The value to format
149
     * @param int $decimals The number of digits to display to the right of the decimal point.
150
     *                                    If decimals is negative, number is rounded to the left of the decimal point.
151
     *                                    If you omit decimals, it is assumed to be 2
152
     *
153
     * @return string
154
     */
155 6
    public static function DOLLAR($value = 0, $decimals = 2)
156
    {
157 6
        $value = Functions::flattenSingleValue($value);
158 6
        $decimals = $decimals === null ? 0 : Functions::flattenSingleValue($decimals);
0 ignored issues
show
introduced by
The condition $decimals === null is always false.
Loading history...
159
160
        // Validate parameters
161 6
        if (!is_numeric($value) || !is_numeric($decimals)) {
162 2
            return Functions::NAN();
163
        }
164 4
        $decimals = floor($decimals);
165
166 4
        $mask = '$#,##0';
167 4
        if ($decimals > 0) {
168 2
            $mask .= '.' . str_repeat('0', $decimals);
0 ignored issues
show
Bug introduced by
$decimals of type double is incompatible with the type integer expected by parameter $multiplier of str_repeat(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

168
            $mask .= '.' . str_repeat('0', /** @scrutinizer ignore-type */ $decimals);
Loading history...
169
        } else {
170 2
            $round = pow(10, abs($decimals));
171 2
            if ($value < 0) {
172
                $round = 0 - $round;
173
            }
174 2
            $value = MathTrig::MROUND($value, $round);
175
        }
176
177 4
        return NumberFormat::toFormattedString($value, $mask);
178
    }
179
180
    /**
181
     * SEARCHSENSITIVE.
182
     *
183
     * @param string $needle The string to look for
184
     * @param string $haystack The string in which to look
185
     * @param int $offset Offset within $haystack
186
     *
187
     * @return string
188
     */
189 13
    public static function SEARCHSENSITIVE($needle, $haystack, $offset = 1)
190
    {
191 13
        $needle = Functions::flattenSingleValue($needle);
192 13
        $haystack = Functions::flattenSingleValue($haystack);
193 13
        $offset = Functions::flattenSingleValue($offset);
194
195 13
        if (!is_bool($needle)) {
196 13
            if (is_bool($haystack)) {
197 2
                $haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE();
198
            }
199
200 13
            if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) {
201 13
                if (StringHelper::countCharacters($needle) === 0) {
202 2
                    return $offset;
203
                }
204
205 11
                $pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8');
206 11
                if ($pos !== false) {
207 9
                    return ++$pos;
208
                }
209
            }
210
        }
211
212 2
        return Functions::VALUE();
213
    }
214
215
    /**
216
     * SEARCHINSENSITIVE.
217
     *
218
     * @param string $needle The string to look for
219
     * @param string $haystack The string in which to look
220
     * @param int $offset Offset within $haystack
221
     *
222
     * @return string
223
     */
224 11
    public static function SEARCHINSENSITIVE($needle, $haystack, $offset = 1)
225
    {
226 11
        $needle = Functions::flattenSingleValue($needle);
227 11
        $haystack = Functions::flattenSingleValue($haystack);
228 11
        $offset = Functions::flattenSingleValue($offset);
229
230 11
        if (!is_bool($needle)) {
231 11
            if (is_bool($haystack)) {
232 2
                $haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE();
233
            }
234
235 11
            if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) {
236 11
                if (StringHelper::countCharacters($needle) === 0) {
237
                    return $offset;
238
                }
239
240 11
                $pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8');
241 11
                if ($pos !== false) {
242 9
                    return ++$pos;
243
                }
244
            }
245
        }
246
247 2
        return Functions::VALUE();
248
    }
249
250
    /**
251
     * FIXEDFORMAT.
252
     *
253
     * @param mixed $value Value to check
254
     * @param int $decimals
255
     * @param bool $no_commas
256
     *
257
     * @return string
258
     */
259 5
    public static function FIXEDFORMAT($value, $decimals = 2, $no_commas = false)
260
    {
261 5
        $value = Functions::flattenSingleValue($value);
262 5
        $decimals = Functions::flattenSingleValue($decimals);
263 5
        $no_commas = Functions::flattenSingleValue($no_commas);
264
265
        // Validate parameters
266 5
        if (!is_numeric($value) || !is_numeric($decimals)) {
267 2
            return Functions::NAN();
268
        }
269 3
        $decimals = floor($decimals);
270
271 3
        $valueResult = round($value, $decimals);
0 ignored issues
show
Bug introduced by
$decimals of type double is incompatible with the type integer expected by parameter $precision of round(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

271
        $valueResult = round($value, /** @scrutinizer ignore-type */ $decimals);
Loading history...
272 3
        if ($decimals < 0) {
273
            $decimals = 0;
274
        }
275 3
        if (!$no_commas) {
276 1
            $valueResult = number_format($valueResult, $decimals);
1 ignored issue
show
Bug introduced by
It seems like $decimals can also be of type double; however, parameter $decimals of number_format() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

276
            $valueResult = number_format($valueResult, /** @scrutinizer ignore-type */ $decimals);
Loading history...
277
        }
278
279 3
        return (string) $valueResult;
280
    }
281
282
    /**
283
     * LEFT.
284
     *
285
     * @param string $value Value
286
     * @param int $chars Number of characters
287
     *
288
     * @return string
289
     */
290 11
    public static function LEFT($value = '', $chars = 1)
291
    {
292 11
        $value = Functions::flattenSingleValue($value);
293 11
        $chars = Functions::flattenSingleValue($chars);
294
295 11
        if ($chars < 0) {
296 1
            return Functions::VALUE();
297
        }
298
299 10
        if (is_bool($value)) {
300 2
            $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
301
        }
302
303 10
        return mb_substr($value, 0, $chars, 'UTF-8');
304
    }
305
306
    /**
307
     * MID.
308
     *
309
     * @param string $value Value
310
     * @param int $start Start character
311
     * @param int $chars Number of characters
312
     *
313
     * @return string
314
     */
315 9
    public static function MID($value = '', $start = 1, $chars = null)
316
    {
317 9
        $value = Functions::flattenSingleValue($value);
318 9
        $start = Functions::flattenSingleValue($start);
319 9
        $chars = Functions::flattenSingleValue($chars);
320
321 9
        if (($start < 1) || ($chars < 0)) {
322 2
            return Functions::VALUE();
323
        }
324
325 7
        if (is_bool($value)) {
326 2
            $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
327
        }
328
329 7
        if (empty($chars)) {
330 1
            return '';
331
        }
332
333 6
        return mb_substr($value, --$start, $chars, 'UTF-8');
334
    }
335
336
    /**
337
     * RIGHT.
338
     *
339
     * @param string $value Value
340
     * @param int $chars Number of characters
341
     *
342
     * @return string
343
     */
344 11
    public static function RIGHT($value = '', $chars = 1)
345
    {
346 11
        $value = Functions::flattenSingleValue($value);
347 11
        $chars = Functions::flattenSingleValue($chars);
348
349 11
        if ($chars < 0) {
350 1
            return Functions::VALUE();
351
        }
352
353 10
        if (is_bool($value)) {
354 2
            $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
355
        }
356
357 10
        return mb_substr($value, mb_strlen($value, 'UTF-8') - $chars, $chars, 'UTF-8');
358
    }
359
360
    /**
361
     * STRINGLENGTH.
362
     *
363
     * @param string $value Value
364
     *
365
     * @return int
366
     */
367 11
    public static function STRINGLENGTH($value = '')
368
    {
369 11
        $value = Functions::flattenSingleValue($value);
370
371 11
        if (is_bool($value)) {
372 2
            $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
373
        }
374
375 11
        return mb_strlen($value, 'UTF-8');
376
    }
377
378
    /**
379
     * LOWERCASE.
380
     *
381
     * Converts a string value to upper case.
382
     *
383
     * @param string $mixedCaseString
384
     *
385
     * @return string
386
     */
387 4
    public static function LOWERCASE($mixedCaseString)
388
    {
389 4
        $mixedCaseString = Functions::flattenSingleValue($mixedCaseString);
390
391 4
        if (is_bool($mixedCaseString)) {
392 2
            $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE();
393
        }
394
395 4
        return StringHelper::strToLower($mixedCaseString);
396
    }
397
398
    /**
399
     * UPPERCASE.
400
     *
401
     * Converts a string value to upper case.
402
     *
403
     * @param string $mixedCaseString
404
     *
405
     * @return string
406
     */
407 4
    public static function UPPERCASE($mixedCaseString)
408
    {
409 4
        $mixedCaseString = Functions::flattenSingleValue($mixedCaseString);
410
411 4
        if (is_bool($mixedCaseString)) {
412 2
            $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE();
413
        }
414
415 4
        return StringHelper::strToUpper($mixedCaseString);
416
    }
417
418
    /**
419
     * PROPERCASE.
420
     *
421
     * Converts a string value to upper case.
422
     *
423
     * @param string $mixedCaseString
424
     *
425
     * @return string
426
     */
427 3
    public static function PROPERCASE($mixedCaseString)
428
    {
429 3
        $mixedCaseString = Functions::flattenSingleValue($mixedCaseString);
430
431 3
        if (is_bool($mixedCaseString)) {
432 2
            $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE();
433
        }
434
435 3
        return StringHelper::strToTitle($mixedCaseString);
436
    }
437
438
    /**
439
     * REPLACE.
440
     *
441
     * @param string $oldText String to modify
442
     * @param int $start Start character
443
     * @param int $chars Number of characters
444
     * @param string $newText String to replace in defined position
445
     *
446
     * @return string
447
     */
448 5
    public static function REPLACE($oldText, $start, $chars, $newText)
449
    {
450 5
        $oldText = Functions::flattenSingleValue($oldText);
451 5
        $start = Functions::flattenSingleValue($start);
452 5
        $chars = Functions::flattenSingleValue($chars);
453 5
        $newText = Functions::flattenSingleValue($newText);
454
455 5
        $left = self::LEFT($oldText, $start - 1);
456 5
        $right = self::RIGHT($oldText, self::STRINGLENGTH($oldText) - ($start + $chars) + 1);
457
458 5
        return $left . $newText . $right;
459
    }
460
461
    /**
462
     * SUBSTITUTE.
463
     *
464
     * @param string $text Value
465
     * @param string $fromText From Value
466
     * @param string $toText To Value
467
     * @param int $instance Instance Number
468
     *
469
     * @return string
470
     */
471 6
    public static function SUBSTITUTE($text = '', $fromText = '', $toText = '', $instance = 0)
472
    {
473 6
        $text = Functions::flattenSingleValue($text);
474 6
        $fromText = Functions::flattenSingleValue($fromText);
475 6
        $toText = Functions::flattenSingleValue($toText);
476 6
        $instance = floor(Functions::flattenSingleValue($instance));
477
478 6
        if ($instance == 0) {
479 4
            return str_replace($fromText, $toText, $text);
480
        }
481
482 2
        $pos = -1;
483 2
        while ($instance > 0) {
484 2
            $pos = mb_strpos($text, $fromText, $pos + 1, 'UTF-8');
485 2
            if ($pos === false) {
486 1
                break;
487
            }
488 1
            --$instance;
489
        }
490
491 2
        if ($pos !== false) {
0 ignored issues
show
introduced by
The condition $pos !== false is always true.
Loading history...
492 1
            return self::REPLACE($text, ++$pos, mb_strlen($fromText, 'UTF-8'), $toText);
493
        }
494
495 1
        return $text;
496
    }
497
498
    /**
499
     * RETURNSTRING.
500
     *
501
     * @param mixed $testValue Value to check
502
     *
503
     * @return null|string
504
     */
505 5
    public static function RETURNSTRING($testValue = '')
506
    {
507 5
        $testValue = Functions::flattenSingleValue($testValue);
508
509 5
        if (is_string($testValue)) {
510 2
            return $testValue;
511
        }
512
513 3
        return null;
514
    }
515
516
    /**
517
     * TEXTFORMAT.
518
     *
519
     * @param mixed $value Value to check
520
     * @param string $format Format mask to use
521
     *
522
     * @return string
523
     */
524 13
    public static function TEXTFORMAT($value, $format)
525
    {
526 13
        $value = Functions::flattenSingleValue($value);
527 13
        $format = Functions::flattenSingleValue($format);
528
529 13
        if ((is_string($value)) && (!is_numeric($value)) && Date::isDateTimeFormatCode($format)) {
530 2
            $value = DateTime::DATEVALUE($value);
531
        }
532
533 13
        return (string) NumberFormat::toFormattedString($value, $format);
534
    }
535
536
    /**
537
     * VALUE.
538
     *
539
     * @param mixed $value Value to check
540
     *
541
     * @return bool
542
     */
543 10
    public static function VALUE($value = '')
544
    {
545 10
        $value = Functions::flattenSingleValue($value);
546
547 10
        if (!is_numeric($value)) {
548 8
            $numberValue = str_replace(
549 8
                StringHelper::getThousandsSeparator(),
550 8
                '',
551 8
                trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode())
552
            );
553 8
            if (is_numeric($numberValue)) {
554 3
                return (float) $numberValue;
0 ignored issues
show
Bug Best Practice introduced by
The expression return (double)$numberValue returns the type double which is incompatible with the documented return type boolean.
Loading history...
555
            }
556
557 5
            $dateSetting = Functions::getReturnDateType();
558 5
            Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
559
560 5
            if (strpos($value, ':') !== false) {
561 2
                $timeValue = DateTime::TIMEVALUE($value);
562 2
                if ($timeValue !== Functions::VALUE()) {
563 2
                    Functions::setReturnDateType($dateSetting);
564
565 2
                    return $timeValue;
566
                }
567
            }
568 3
            $dateValue = DateTime::DATEVALUE($value);
569 3
            if ($dateValue !== Functions::VALUE()) {
570 1
                Functions::setReturnDateType($dateSetting);
571
572 1
                return $dateValue;
573
            }
574 2
            Functions::setReturnDateType($dateSetting);
575
576 2
            return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type boolean.
Loading history...
577
        }
578
579 2
        return (float) $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return (double)$value returns the type double which is incompatible with the documented return type boolean.
Loading history...
580
    }
581
582
    /**
583
     * NUMBERVALUE.
584
     *
585
     * @param mixed $value Value to check
586
     * @param string $decimalSeparator decimal separator, defaults to locale defined value
587
     * @param string $groupSeparator group/thosands separator, defaults to locale defined value
588
     *
589
     * @return float|string
590
     */
591 12
    public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null)
592
    {
593 12
        $value = Functions::flattenSingleValue($value);
594 12
        $decimalSeparator = Functions::flattenSingleValue($decimalSeparator);
595 12
        $groupSeparator = Functions::flattenSingleValue($groupSeparator);
596
597 12
        if (!is_numeric($value)) {
598 12
            $decimalSeparator = empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : $decimalSeparator;
599 12
            $groupSeparator = empty($groupSeparator) ? StringHelper::getThousandsSeparator() : $groupSeparator;
600
601 12
            $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE);
602 12
            if ($decimalPositions > 1) {
603 2
                return Functions::VALUE();
604
            }
605 10
            $decimalOffset = array_pop($matches[0])[1];
606 10
            if (strpos($value, $groupSeparator, $decimalOffset) !== false) {
607 1
                return Functions::VALUE();
608
            }
609
610 9
            $value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value);
611
612
            // Handle the special case of trailing % signs
613 9
            $percentageString = rtrim($value, '%');
614 9
            if (!is_numeric($percentageString)) {
615 2
                return Functions::VALUE();
616
            }
617
618 7
            $percentageAdjustment = strlen($value) - strlen($percentageString);
619 7
            if ($percentageAdjustment) {
620 3
                $value = (float) $percentageString;
621 3
                $value /= pow(10, $percentageAdjustment * 2);
622
            }
623
        }
624
625 7
        return (float) $value;
626
    }
627
628
    /**
629
     * Compares two text strings and returns TRUE if they are exactly the same, FALSE otherwise.
630
     * EXACT is case-sensitive but ignores formatting differences.
631
     * Use EXACT to test text being entered into a document.
632
     *
633
     * @param $value1
634
     * @param $value2
635
     *
636
     * @return bool
637
     */
638 7
    public static function EXACT($value1, $value2)
639
    {
640 7
        $value1 = Functions::flattenSingleValue($value1);
641 7
        $value2 = Functions::flattenSingleValue($value2);
642
643 7
        return (string) $value2 === (string) $value1;
644
    }
645
646
    /**
647
     * TEXTJOIN.
648
     *
649
     * @param mixed $delimiter
650
     * @param mixed $ignoreEmpty
651
     * @param mixed $args
652
     *
653
     * @return string
654
     */
655 6
    public static function TEXTJOIN($delimiter, $ignoreEmpty, ...$args)
656
    {
657
        // Loop through arguments
658 6
        $aArgs = Functions::flattenArray($args);
659 6
        foreach ($aArgs as $key => &$arg) {
660 6
            if ($ignoreEmpty && trim($arg) == '') {
661 2
                unset($aArgs[$key]);
662 6
            } elseif (is_bool($arg)) {
663 3
                $arg = self::convertBooleanValue($arg);
664
            }
665
        }
666
667 6
        return implode($delimiter, $aArgs);
668
    }
669
}
670