Completed
Push — master ( 71a6f6...e9cd7a )
by Adrien
9s
created

CellValueFormatter::formatBooleanCellValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
ccs 3
cts 3
cp 1
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Box\Spout\Reader\XLSX\Helper;
4
5
/**
6
 * Class CellValueFormatter
7
 * This class provides helper functions to format cell values
8
 *
9
 * @package Box\Spout\Reader\XLSX\Helper
10
 */
11
class CellValueFormatter
12
{
13
    /** Definition of all possible cell types */
14
    const CELL_TYPE_INLINE_STRING = 'inlineStr';
15
    const CELL_TYPE_STR = 'str';
16
    const CELL_TYPE_SHARED_STRING = 's';
17
    const CELL_TYPE_BOOLEAN = 'b';
18
    const CELL_TYPE_NUMERIC = 'n';
19
    const CELL_TYPE_DATE = 'd';
20
    const CELL_TYPE_ERROR = 'e';
21
22
    /** Definition of XML nodes names used to parse data */
23
    const XML_NODE_VALUE = 'v';
24
    const XML_NODE_INLINE_STRING_VALUE = 't';
25
26
    /** Definition of XML attributes used to parse data */
27
    const XML_ATTRIBUTE_TYPE = 't';
28
    const XML_ATTRIBUTE_STYLE_ID = 's';
29
30
    /** Constants used for date formatting */
31
    const NUM_SECONDS_IN_ONE_DAY = 86400;
32
33
    /**
34
     * February 29th, 1900 is NOT a leap year but Excel thinks it is...
35
     * @see https://en.wikipedia.org/wiki/Year_1900_problem#Microsoft_Excel
36
     */
37
    const ERRONEOUS_EXCEL_LEAP_YEAR_DAY = 60;
38
39
    /** @var SharedStringsHelper Helper to work with shared strings */
40
    protected $sharedStringsHelper;
41
42
    /** @var StyleHelper Helper to work with styles */
43
    protected $styleHelper;
44
45
    /** @var \Box\Spout\Common\Escaper\XLSX Used to unescape XML data */
46
    protected $escaper;
47
48
    /**
49
     * @param SharedStringsHelper $sharedStringsHelper Helper to work with shared strings
50
     * @param StyleHelper $styleHelper Helper to work with styles
51
     */
52 147
    public function __construct($sharedStringsHelper, $styleHelper)
53
    {
54 147
        $this->sharedStringsHelper = $sharedStringsHelper;
55 147
        $this->styleHelper = $styleHelper;
56
57
        /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
58 147
        $this->escaper = new \Box\Spout\Common\Escaper\XLSX();
59 147
    }
60
61
    /**
62
     * Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
63
     *
64
     * @param \DOMNode $node
65
     * @return string|int|float|bool|\DateTime|null The value associated with the cell (null when the cell has an error)
66
     */
67 87
    public function extractAndFormatNodeValue($node)
68
    {
69
        // Default cell type is "n"
70 87
        $cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE) ?: self::CELL_TYPE_NUMERIC;
71 87
        $cellStyleId = intval($node->getAttribute(self::XML_ATTRIBUTE_STYLE_ID));
72 87
        $vNodeValue = $this->getVNodeValue($node);
73
74 87
        if (($vNodeValue === '') && ($cellType !== self::CELL_TYPE_INLINE_STRING)) {
75 3
            return $vNodeValue;
76
        }
77
78
        switch ($cellType) {
79 87
            case self::CELL_TYPE_INLINE_STRING:
80 18
                return $this->formatInlineStringCellValue($node);
81 72
            case self::CELL_TYPE_SHARED_STRING:
82 39
                return $this->formatSharedStringCellValue($vNodeValue);
83 45
            case self::CELL_TYPE_STR:
84 3
                return $this->formatStrCellValue($vNodeValue);
85 45
            case self::CELL_TYPE_BOOLEAN:
86 3
                return $this->formatBooleanCellValue($vNodeValue);
87 45
            case self::CELL_TYPE_NUMERIC:
88 45
                return $this->formatNumericCellValue($vNodeValue, $cellStyleId);
89 3
            case self::CELL_TYPE_DATE:
90 3
                return $this->formatDateCellValue($vNodeValue);
91 3
            default:
92 3
                return null;
93 3
        }
94
    }
95
96
    /**
97
     * Returns the cell's string value from a node's nested value node
98
     *
99
     * @param \DOMNode $node
100
     * @return string The value associated with the cell
101
     */
102 87
    protected function getVNodeValue($node)
103
    {
104
        // for cell types having a "v" tag containing the value.
105
        // if not, the returned value should be empty string.
106 87
        $vNode = $node->getElementsByTagName(self::XML_NODE_VALUE)->item(0);
107 87
        return ($vNode !== null) ? $vNode->nodeValue : '';
108
    }
109
110
    /**
111
     * Returns the cell String value where string is inline.
112
     *
113
     * @param \DOMNode $node
114
     * @return string The value associated with the cell (null when the cell has an error)
115
     */
116 30
    protected function formatInlineStringCellValue($node)
117
    {
118
        // inline strings are formatted this way:
119
        // <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t></is></c>
120 30
        $tNode = $node->getElementsByTagName(self::XML_NODE_INLINE_STRING_VALUE)->item(0);
121 30
        $cellValue = $this->escaper->unescape($tNode->nodeValue);
122 30
        return $cellValue;
123
    }
124
125
    /**
126
     * Returns the cell String value from shared-strings file using nodeValue index.
127
     *
128
     * @param string $nodeValue
129
     * @return string The value associated with the cell (null when the cell has an error)
130
     */
131 39
    protected function formatSharedStringCellValue($nodeValue)
132
    {
133
        // shared strings are formatted this way:
134
        // <c r="A1" t="s"><v>[SHARED_STRING_INDEX]</v></c>
135 39
        $sharedStringIndex = intval($nodeValue);
136 39
        $escapedCellValue = $this->sharedStringsHelper->getStringAtIndex($sharedStringIndex);
137 39
        $cellValue = $this->escaper->unescape($escapedCellValue);
138 39
        return $cellValue;
139
    }
140
141
    /**
142
     * Returns the cell String value, where string is stored in value node.
143
     *
144
     * @param string $nodeValue
145
     * @return string The value associated with the cell (null when the cell has an error)
146
     */
147 3
    protected function formatStrCellValue($nodeValue)
148
    {
149 3
        $escapedCellValue = trim($nodeValue);
150 3
        $cellValue = $this->escaper->unescape($escapedCellValue);
151 3
        return $cellValue;
152
    }
153
154
    /**
155
     * Returns the cell Numeric value from string of nodeValue.
156
     * The value can also represent a timestamp and a DateTime will be returned.
157
     *
158
     * @param string $nodeValue
159
     * @param int $cellStyleId 0 being the default style
160
     * @return int|float|\DateTime|null The value associated with the cell
0 ignored issues
show
Documentation introduced by
Should the return type not be null|\DateTime|false|integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
161
     */
162 84
    protected function formatNumericCellValue($nodeValue, $cellStyleId)
163
    {
164
        // Numeric values can represent numbers as well as timestamps.
165
        // We need to look at the style of the cell to determine whether it is one or the other.
166 84
        $shouldFormatAsDate = $this->styleHelper->shouldFormatNumericValueAsDate($cellStyleId);
167
168 84
        if ($shouldFormatAsDate) {
169 30
            return $this->formatExcelTimestampValue(floatval($nodeValue));
170
        } else {
171 54
            $nodeIntValue = intval($nodeValue);
172 54
            return ($nodeIntValue == $nodeValue) ? $nodeIntValue : floatval($nodeValue);
173
        }
174
    }
175
176
    /**
177
     * Returns a cell's PHP Date value, associated to the given timestamp.
178
     * NOTE: The timestamp is a float representing the number of days since January 1st, 1900.
179
     *
180
     * @param float $nodeValue
181
     * @return \DateTime|null The value associated with the cell or NULL if invalid date value
0 ignored issues
show
Documentation introduced by
Should the return type not be null|\DateTime|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
182
     */
183 30
    protected function formatExcelTimestampValue($nodeValue)
184
    {
185
        // Fix for the erroneous leap year in Excel
186 30
        if (ceil($nodeValue) > self::ERRONEOUS_EXCEL_LEAP_YEAR_DAY) {
187 15
            --$nodeValue;
188 15
        }
189
190
        // The value 1.0 represents 1900-01-01. Numbers below 1.0 are not valid Excel dates.
191 30
        if ($nodeValue < 1.0) {
192 9
            return null;
193
        }
194
195
        // Do not use any unix timestamps for calculation to prevent
196
        // issues with numbers exceeding 2^31.
197 21
        $secondsRemainder = fmod($nodeValue, 1) * self::NUM_SECONDS_IN_ONE_DAY;
198 21
        $secondsRemainder = round($secondsRemainder, 0);
199
200
        try {
201 21
            $cellValue = \DateTime::createFromFormat('|Y-m-d', '1899-12-31');
202 21
            $cellValue->modify('+' . intval($nodeValue) . 'days');
203 21
            $cellValue->modify('+' . $secondsRemainder . 'seconds');
204
205 21
            return $cellValue;
206
        } catch (\Exception $e) {
207
            return null;
208
        }
209
    }
210
211
    /**
212
     * Returns the cell Boolean value from a specific node's Value.
213
     *
214
     * @param string $nodeValue
215
     * @return bool The value associated with the cell
216
     */
217 3
    protected function formatBooleanCellValue($nodeValue)
218
    {
219
        // !! is similar to boolval()
220 3
        $cellValue = !!$nodeValue;
221 3
        return $cellValue;
222
    }
223
224
    /**
225
     * Returns a cell's PHP Date value, associated to the given stored nodeValue.
226
     *
227
     * @param string $nodeValue
228
     * @return \DateTime|null The value associated with the cell or NULL if invalid date value
229
     */
230 3
    protected function formatDateCellValue($nodeValue)
231
    {
232
        // Mitigate thrown Exception on invalid date-time format (http://php.net/manual/en/datetime.construct.php)
233
        try {
234 3
            $cellValue = new \DateTime($nodeValue);
235 3
            return $cellValue;
236 3
        } catch (\Exception $e) {
237 3
            return null;
238
        }
239
    }
240
}
241