PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.11.10
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.11.10
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / vendor / openspout / openspout / src / Reader / XLSX / Helper / CellValueFormatter.php

CellValueFormatter.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.11.10, at vendor/openspout/openspout/src/Reader/XLSX/Helper/CellValueFormatter.php

316 lines 11.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace OpenSpout\Reader\XLSX\Helper;
4
5 use OpenSpout\Reader\Exception\InvalidValueException;
6 use OpenSpout\Reader\XLSX\Manager\SharedStringsManager;
7 use OpenSpout\Reader\XLSX\Manager\StyleManager;
8
9 /**
10 * This class provides helper functions to format cell values.
11 */
12 class CellValueFormatter
13 {
14 /** Definition of all possible cell types */
15 public const CELL_TYPE_INLINE_STRING = 'inlineStr';
16 public const CELL_TYPE_STR = 'str';
17 public const CELL_TYPE_SHARED_STRING = 's';
18 public const CELL_TYPE_BOOLEAN = 'b';
19 public const CELL_TYPE_NUMERIC = 'n';
20 public const CELL_TYPE_DATE = 'd';
21 public const CELL_TYPE_ERROR = 'e';
22
23 /** Definition of XML nodes names used to parse data */
24 public const XML_NODE_VALUE = 'v';
25 public const XML_NODE_INLINE_STRING_VALUE = 't';
26
27 /** Definition of XML attributes used to parse data */
28 public const XML_ATTRIBUTE_TYPE = 't';
29 public const XML_ATTRIBUTE_STYLE_ID = 's';
30
31 /** Constants used for date formatting */
32 public const NUM_SECONDS_IN_ONE_DAY = 86400;
33
34 /** @var SharedStringsManager Manages shared strings */
35 protected $sharedStringsManager;
36
37 /** @var StyleManager Manages styles */
38 protected $styleManager;
39
40 /** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
41 protected $shouldFormatDates;
42
43 /** @var bool Whether date/time values should use a calendar starting in 1904 instead of 1900 */
44 protected $shouldUse1904Dates;
45
46 /** @var \OpenSpout\Common\Helper\Escaper\XLSX Used to unescape XML data */
47 protected $escaper;
48
49 /**
50 * @param SharedStringsManager $sharedStringsManager Manages shared strings
51 * @param StyleManager $styleManager Manages styles
52 * @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
53 * @param bool $shouldUse1904Dates Whether date/time values should use a calendar starting in 1904 instead of 1900
54 * @param \OpenSpout\Common\Helper\Escaper\XLSX $escaper Used to unescape XML data
55 */
56 public function __construct($sharedStringsManager, $styleManager, $shouldFormatDates, $shouldUse1904Dates, $escaper)
57 {
58 $this->sharedStringsManager = $sharedStringsManager;
59 $this->styleManager = $styleManager;
60 $this->shouldFormatDates = $shouldFormatDates;
61 $this->shouldUse1904Dates = $shouldUse1904Dates;
62 $this->escaper = $escaper;
63 }
64
65 /**
66 * Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
67 *
68 * @param \DOMElement $node
69 *
70 * @throws InvalidValueException If the value is not valid
71 *
72 * @return bool|\DateTime|float|int|string The value associated with the cell
73 */
74 public function extractAndFormatNodeValue($node)
75 {
76 // Default cell type is "n"
77 $cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE) ?: self::CELL_TYPE_NUMERIC;
78 $cellStyleId = (int) $node->getAttribute(self::XML_ATTRIBUTE_STYLE_ID);
79 $vNodeValue = $this->getVNodeValue($node);
80
81 if (('' === $vNodeValue) && (self::CELL_TYPE_INLINE_STRING !== $cellType)) {
82 return $vNodeValue;
83 }
84
85 switch ($cellType) {
86 case self::CELL_TYPE_INLINE_STRING:
87 return $this->formatInlineStringCellValue($node);
88
89 case self::CELL_TYPE_SHARED_STRING:
90 return $this->formatSharedStringCellValue($vNodeValue);
91
92 case self::CELL_TYPE_STR:
93 return $this->formatStrCellValue($vNodeValue);
94
95 case self::CELL_TYPE_BOOLEAN:
96 return $this->formatBooleanCellValue($vNodeValue);
97
98 case self::CELL_TYPE_NUMERIC:
99 return $this->formatNumericCellValue($vNodeValue, $cellStyleId);
100
101 case self::CELL_TYPE_DATE:
102 return $this->formatDateCellValue($vNodeValue);
103
104 default:
105 throw new InvalidValueException($vNodeValue);
106 }
107 }
108
109 /**
110 * Returns the cell's string value from a node's nested value node.
111 *
112 * @param \DOMElement $node
113 *
114 * @return string The value associated with the cell
115 */
116 protected function getVNodeValue($node)
117 {
118 // for cell types having a "v" tag containing the value.
119 // if not, the returned value should be empty string.
120 $vNode = $node->getElementsByTagName(self::XML_NODE_VALUE)->item(0);
121
122 return (null !== $vNode) ? $vNode->nodeValue : '';
123 }
124
125 /**
126 * Returns the cell String value where string is inline.
127 *
128 * @param \DOMElement $node
129 *
130 * @return string The value associated with the cell
131 */
132 protected function formatInlineStringCellValue($node)
133 {
134 // inline strings are formatted this way (they can contain any number of <t> nodes):
135 // <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t><t>[INLINE_STRING_2]</t></is></c>
136 $tNodes = $node->getElementsByTagName(self::XML_NODE_INLINE_STRING_VALUE);
137
138 $cellValue = '';
139 for ($i = 0; $i < $tNodes->count(); ++$i) {
140 $tNode = $tNodes->item($i);
141 $cellValue .= $this->escaper->unescape($tNode->nodeValue);
142 }
143
144 return $cellValue;
145 }
146
147 /**
148 * Returns the cell String value from shared-strings file using nodeValue index.
149 *
150 * @param string $nodeValue
151 *
152 * @return string The value associated with the cell
153 */
154 protected function formatSharedStringCellValue($nodeValue)
155 {
156 // shared strings are formatted this way:
157 // <c r="A1" t="s"><v>[SHARED_STRING_INDEX]</v></c>
158 $sharedStringIndex = (int) $nodeValue;
159 $escapedCellValue = $this->sharedStringsManager->getStringAtIndex($sharedStringIndex);
160
161 return $this->escaper->unescape($escapedCellValue);
162 }
163
164 /**
165 * Returns the cell String value, where string is stored in value node.
166 *
167 * @param string $nodeValue
168 *
169 * @return string The value associated with the cell
170 */
171 protected function formatStrCellValue($nodeValue)
172 {
173 $escapedCellValue = trim($nodeValue);
174
175 return $this->escaper->unescape($escapedCellValue);
176 }
177
178 /**
179 * Returns the cell Numeric value from string of nodeValue.
180 * The value can also represent a timestamp and a DateTime will be returned.
181 *
182 * @param string $nodeValue
183 * @param int $cellStyleId 0 being the default style
184 *
185 * @return \DateTime|float|int The value associated with the cell
186 */
187 protected function formatNumericCellValue($nodeValue, $cellStyleId)
188 {
189 // Numeric values can represent numbers as well as timestamps.
190 // We need to look at the style of the cell to determine whether it is one or the other.
191 $shouldFormatAsDate = $this->styleManager->shouldFormatNumericValueAsDate($cellStyleId);
192
193 if ($shouldFormatAsDate) {
194 $cellValue = $this->formatExcelTimestampValue((float) $nodeValue, $cellStyleId);
195 } else {
196 $nodeIntValue = (int) $nodeValue;
197 $nodeFloatValue = (float) $nodeValue;
198 $cellValue = ((float) $nodeIntValue === $nodeFloatValue) ? $nodeIntValue : $nodeFloatValue;
199 }
200
201 return $cellValue;
202 }
203
204 /**
205 * Returns a cell's PHP Date value, associated to the given timestamp.
206 * NOTE: The timestamp is a float representing the number of days since the base Excel date:
207 * Dec 30th 1899, 1900 or Jan 1st, 1904, depending on the Workbook setting.
208 * NOTE: The timestamp can also represent a time, if it is a value between 0 and 1.
209 *
210 * @see ECMA-376 Part 1 - §18.17.4
211 *
212 * @param float $nodeValue
213 * @param int $cellStyleId 0 being the default style
214 *
215 * @throws InvalidValueException If the value is not a valid timestamp
216 *
217 * @return \DateTime The value associated with the cell
218 */
219 protected function formatExcelTimestampValue($nodeValue, $cellStyleId)
220 {
221 if ($this->isValidTimestampValue($nodeValue)) {
222 $cellValue = $this->formatExcelTimestampValueAsDateTimeValue($nodeValue, $cellStyleId);
223 } else {
224 throw new InvalidValueException($nodeValue);
225 }
226
227 return $cellValue;
228 }
229
230 /**
231 * Returns whether the given timestamp is supported by SpreadsheetML.
232 *
233 * @see ECMA-376 Part 1 - §18.17.4 - this specifies the timestamp boundaries.
234 *
235 * @param float $timestampValue
236 *
237 * @return bool
238 */
239 protected function isValidTimestampValue($timestampValue)
240 {
241 // @NOTE: some versions of Excel don't support negative dates (e.g. Excel for Mac 2011)
242 return
243 $this->shouldUse1904Dates && $timestampValue >= -695055 && $timestampValue <= 2957003.9999884
244 || !$this->shouldUse1904Dates && $timestampValue >= -693593 && $timestampValue <= 2958465.9999884
245 ;
246 }
247
248 /**
249 * Returns a cell's PHP DateTime value, associated to the given timestamp.
250 * Only the time value matters. The date part is set to the base Excel date:
251 * Dec 30th 1899, 1900 or Jan 1st, 1904, depending on the Workbook setting.
252 *
253 * @param float $nodeValue
254 * @param int $cellStyleId 0 being the default style
255 *
256 * @return \DateTime|string The value associated with the cell
257 */
258 protected function formatExcelTimestampValueAsDateTimeValue($nodeValue, $cellStyleId)
259 {
260 $baseDate = $this->shouldUse1904Dates ? '1904-01-01' : '1899-12-30';
261
262 $daysSinceBaseDate = (int) $nodeValue;
263 $timeRemainder = fmod($nodeValue, 1);
264 $secondsRemainder = round($timeRemainder * self::NUM_SECONDS_IN_ONE_DAY, 0);
265
266 $dateObj = \DateTime::createFromFormat('|Y-m-d', $baseDate);
267 $dateObj->modify('+'.$daysSinceBaseDate.'days');
268 $dateObj->modify('+'.$secondsRemainder.'seconds');
269
270 if ($this->shouldFormatDates) {
271 $styleNumberFormatCode = $this->styleManager->getNumberFormatCode($cellStyleId);
272 $phpDateFormat = DateFormatHelper::toPHPDateFormat($styleNumberFormatCode);
273 $cellValue = $dateObj->format($phpDateFormat);
274 } else {
275 $cellValue = $dateObj;
276 }
277
278 return $cellValue;
279 }
280
281 /**
282 * Returns the cell Boolean value from a specific node's Value.
283 *
284 * @param string $nodeValue
285 *
286 * @return bool The value associated with the cell
287 */
288 protected function formatBooleanCellValue($nodeValue)
289 {
290 return (bool) $nodeValue;
291 }
292
293 /**
294 * Returns a cell's PHP Date value, associated to the given stored nodeValue.
295 *
296 * @see ECMA-376 Part 1 - §18.17.4
297 *
298 * @param string $nodeValue ISO 8601 Date string
299 *
300 * @throws InvalidValueException If the value is not a valid date
301 *
302 * @return \DateTime|string The value associated with the cell
303 */
304 protected function formatDateCellValue($nodeValue)
305 {
306 // Mitigate thrown Exception on invalid date-time format (http://php.net/manual/en/datetime.construct.php)
307 try {
308 $cellValue = ($this->shouldFormatDates) ? $nodeValue : new \DateTime($nodeValue);
309 } catch (\Exception $e) {
310 throw new InvalidValueException($nodeValue);
311 }
312
313 return $cellValue;
314 }
315 }
316