Completed
Push — master ( 5072ff...a24179 )
by Mark
37s queued 28s
created

AdvancedValueBinder::setImproperFraction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 2
dl 0
loc 18
ccs 11
cts 11
cp 1
crap 2
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Cell;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Calculation\Engine\FormattedNumber;
7
use PhpOffice\PhpSpreadsheet\RichText\RichText;
8
use PhpOffice\PhpSpreadsheet\Shared\Date;
9
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
10
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
11
12
class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
13
{
14
    /**
15
     * Bind value to a cell.
16
     *
17
     * @param Cell $cell Cell to bind value to
18
     * @param mixed $value Value to bind in cell
19
     *
20
     * @return bool
21
     */
22 45
    public function bindValue(Cell $cell, $value = null)
23
    {
24 45
        if ($value === null) {
25 1
            return parent::bindValue($cell, $value);
26 44
        } elseif (is_string($value)) {
27
            // sanitize UTF-8 strings
28 44
            $value = StringHelper::sanitizeUTF8($value);
29
        }
30
31
        // Find out data type
32 44
        $dataType = parent::dataTypeForValue($value);
33
34
        // Style logic - strings
35 44
        if ($dataType === DataType::TYPE_STRING && !$value instanceof RichText) {
36
            //    Test for booleans using locale-setting
37 44
            if (StringHelper::strToUpper($value) === Calculation::getTRUE()) {
38 3
                $cell->setValueExplicit(true, DataType::TYPE_BOOL);
39
40 3
                return true;
41 44
            } elseif (StringHelper::strToUpper($value) === Calculation::getFALSE()) {
42 2
                $cell->setValueExplicit(false, DataType::TYPE_BOOL);
43
44 2
                return true;
45
            }
46
47
            // Check for fractions
48 42
            if (preg_match('/^([+-]?)\s*(\d+)\s?\/\s*(\d+)$/', $value, $matches)) {
49 8
                return $this->setProperFraction($matches, $cell);
50 35
            } elseif (preg_match('/^([+-]?)(\d*) +(\d*)\s?\/\s*(\d*)$/', $value, $matches)) {
51 7
                return $this->setImproperFraction($matches, $cell);
52
            }
53
54 29
            $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator());
55 29
            $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator());
56
57
            // Check for percentage
58 29
            if (preg_match('/^\-?\d*' . $decimalSeparator . '?\d*\s?\%$/', preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value))) {
59 11
                return $this->setPercentage(preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $cell);
60
            }
61
62
            // Check for currency
63 20
            if (preg_match(FormattedNumber::currencyMatcherRegexp(), preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $matches, PREG_UNMATCHED_AS_NULL)) {
64
                // Convert value to number
65 12
                $sign = ($matches['PrefixedSign'] ?? $matches['PrefixedSign2'] ?? $matches['PostfixedSign']) ?? null;
66 12
                $currencyCode = $matches['PrefixedCurrency'] ?? $matches['PostfixedCurrency'];
67 12
                $value = (float) ($sign . trim(str_replace([$decimalSeparator, $currencyCode, ' ', '-'], ['.', '', '', ''], preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value)))); // @phpstan-ignore-line
68
69 12
                return $this->setCurrency($value, $cell, $currencyCode); // @phpstan-ignore-line
70
            }
71
72
            // Check for time without seconds e.g. '9:45', '09:45'
73 9
            if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d$/', $value)) {
74 5
                return $this->setTimeHoursMinutes($value, $cell);
75
            }
76
77
            // Check for time with seconds '9:45:59', '09:45:59'
78 6
            if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d:[0-5]\d$/', $value)) {
79 3
                return $this->setTimeHoursMinutesSeconds($value, $cell);
80
            }
81
82
            // Check for datetime, e.g. '2008-12-31', '2008-12-31 15:59', '2008-12-31 15:59:10'
83 4
            if (($d = Date::stringToExcel($value)) !== false) {
84
                // Convert value to number
85 2
                $cell->setValueExplicit($d, DataType::TYPE_NUMERIC);
86
                // Determine style. Either there is a time part or not. Look for ':'
87 2
                if (strpos($value, ':') !== false) {
88 1
                    $formatCode = 'yyyy-mm-dd h:mm';
89
                } else {
90 2
                    $formatCode = 'yyyy-mm-dd';
91
                }
92 2
                $cell->getWorksheet()->getStyle($cell->getCoordinate())
93 2
                    ->getNumberFormat()->setFormatCode($formatCode);
94
95 2
                return true;
96
            }
97
98
            // Check for newline character "\n"
99 4
            if (strpos($value, "\n") !== false) {
100 1
                $cell->setValueExplicit($value, DataType::TYPE_STRING);
101
                // Set style
102 1
                $cell->getWorksheet()->getStyle($cell->getCoordinate())
103 1
                    ->getAlignment()->setWrapText(true);
104
105 1
                return true;
106
            }
107
        }
108
109
        // Not bound yet? Use parent...
110 4
        return parent::bindValue($cell, $value);
111
    }
112
113 7
    protected function setImproperFraction(array $matches, Cell $cell): bool
114
    {
115
        // Convert value to number
116 7
        $value = $matches[2] + ($matches[3] / $matches[4]);
117 7
        if ($matches[1] === '-') {
118 3
            $value = 0 - $value;
119
        }
120 7
        $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC);
121
122
        // Build the number format mask based on the size of the matched values
123 7
        $dividend = str_repeat('?', strlen($matches[3]));
124 7
        $divisor = str_repeat('?', strlen($matches[4]));
125 7
        $fractionMask = "# {$dividend}/{$divisor}";
126
        // Set style
127 7
        $cell->getWorksheet()->getStyle($cell->getCoordinate())
128 7
            ->getNumberFormat()->setFormatCode($fractionMask);
129
130 7
        return true;
131
    }
132
133 8
    protected function setProperFraction(array $matches, Cell $cell): bool
134
    {
135
        // Convert value to number
136 8
        $value = $matches[2] / $matches[3];
137 8
        if ($matches[1] === '-') {
138 3
            $value = 0 - $value;
139
        }
140 8
        $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC);
141
142
        // Build the number format mask based on the size of the matched values
143 8
        $dividend = str_repeat('?', strlen($matches[2]));
144 8
        $divisor = str_repeat('?', strlen($matches[3]));
145 8
        $fractionMask = "{$dividend}/{$divisor}";
146
        // Set style
147 8
        $cell->getWorksheet()->getStyle($cell->getCoordinate())
148 8
            ->getNumberFormat()->setFormatCode($fractionMask);
149
150 8
        return true;
151
    }
152
153 11
    protected function setPercentage(string $value, Cell $cell): bool
154
    {
155
        // Convert value to number
156 11
        $value = ((float) str_replace('%', '', $value)) / 100;
157 11
        $cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
158
159
        // Set style
160 11
        $cell->getWorksheet()->getStyle($cell->getCoordinate())
161 11
            ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_PERCENTAGE_00);
162
163 11
        return true;
164
    }
165
166 12
    protected function setCurrency(float $value, Cell $cell, string $currencyCode): bool
167
    {
168 12
        $cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
169
        // Set style
170 12
        $cell->getWorksheet()->getStyle($cell->getCoordinate())
171 12
            ->getNumberFormat()->setFormatCode(
172 12
                str_replace('$', '[$' . $currencyCode . ']', NumberFormat::FORMAT_CURRENCY_USD_SIMPLE)
173 12
            );
174
175 12
        return true;
176
    }
177
178 5
    protected function setTimeHoursMinutes(string $value, Cell $cell): bool
179
    {
180
        // Convert value to number
181 5
        [$hours, $minutes] = explode(':', $value);
182 5
        $hours = (int) $hours;
183 5
        $minutes = (int) $minutes;
184 5
        $days = ($hours / 24) + ($minutes / 1440);
185 5
        $cell->setValueExplicit($days, DataType::TYPE_NUMERIC);
186
187
        // Set style
188 5
        $cell->getWorksheet()->getStyle($cell->getCoordinate())
189 5
            ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME3);
190
191 5
        return true;
192
    }
193
194 3
    protected function setTimeHoursMinutesSeconds(string $value, Cell $cell): bool
195
    {
196
        // Convert value to number
197 3
        [$hours, $minutes, $seconds] = explode(':', $value);
198 3
        $hours = (int) $hours;
199 3
        $minutes = (int) $minutes;
200 3
        $seconds = (int) $seconds;
201 3
        $days = ($hours / 24) + ($minutes / 1440) + ($seconds / 86400);
202 3
        $cell->setValueExplicit($days, DataType::TYPE_NUMERIC);
203
204
        // Set style
205 3
        $cell->getWorksheet()->getStyle($cell->getCoordinate())
206 3
            ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME4);
207
208 3
        return true;
209
    }
210
}
211