PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Reader / Gnumeric.php

Gnumeric.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Reader/Gnumeric.php

660 lines 20.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TablePress\PhpOffice\PhpSpreadsheet\Reader;
4
5 use TablePress\PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6 use TablePress\PhpOffice\PhpSpreadsheet\Cell\DataType;
7 use TablePress\PhpOffice\PhpSpreadsheet\DefinedName;
8 use TablePress\PhpOffice\PhpSpreadsheet\Reader\Gnumeric\PageSetup;
9 use TablePress\PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Properties;
10 use TablePress\PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Styles;
11 use TablePress\PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
12 use TablePress\PhpOffice\PhpSpreadsheet\ReferenceHelper;
13 use TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText;
14 use TablePress\PhpOffice\PhpSpreadsheet\Shared\File;
15 use TablePress\PhpOffice\PhpSpreadsheet\Spreadsheet;
16 use TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
17 use SimpleXMLElement;
18 use XMLReader;
19
20 class Gnumeric extends BaseReader
21 {
22 const NAMESPACE_GNM = 'http://www.gnumeric.org/v10.dtd'; // gmr in old sheets
23
24 const NAMESPACE_XSI = 'http://www.w3.org/2001/XMLSchema-instance';
25
26 const NAMESPACE_OFFICE = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0';
27
28 const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink';
29
30 const NAMESPACE_DC = 'http://purl.org/dc/elements/1.1/';
31
32 const NAMESPACE_META = 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0';
33
34 const NAMESPACE_OOO = 'http://openoffice.org/2004/office';
35
36 const GNM_SHEET_VISIBILITY_VISIBLE = 'GNM_SHEET_VISIBILITY_VISIBLE';
37 const GNM_SHEET_VISIBILITY_HIDDEN = 'GNM_SHEET_VISIBILITY_HIDDEN';
38
39 /**
40 * Shared Expressions.
41 *
42 * @var array<array{column: int, row: int, formula:string}>
43 */
44 private array $expressions = [];
45
46 /**
47 * Spreadsheet shared across all functions.
48 */
49 private Spreadsheet $spreadsheet;
50
51 private ReferenceHelper $referenceHelper;
52
53 /**
54 * @deprecated 5.10.0 No longer used, replaced by const which is not user-accessible.
55 *
56 * @var array{'dataType': string[]}
57 */
58 public static array $mappings = self::MAPPINGS;
59
60 private const MAPPINGS = [
61 'dataType' => [
62 '10' => DataType::TYPE_NULL,
63 '20' => DataType::TYPE_BOOL,
64 '30' => DataType::TYPE_NUMERIC, // Integer doesn't exist in Excel
65 '40' => DataType::TYPE_NUMERIC, // Float
66 '50' => DataType::TYPE_ERROR,
67 '60' => DataType::TYPE_STRING,
68 //'70': // Cell Range
69 //'80': // Array
70 ],
71 ];
72
73 protected int $maxLength;
74
75 private const LENGTH_MULTIPLIER = [
76 'G' => 1024 * 1024 * 1024,
77 'M' => 1024 * 1024,
78 'K' => 1024,
79 ];
80
81 /**
82 * Create a new Gnumeric.
83 */
84 public function __construct()
85 {
86 parent::__construct();
87 $this->referenceHelper = ReferenceHelper::getInstance();
88 $this->securityScanner = XmlScanner::getInstance($this);
89 $limit = ini_get('memory_limit') ?: '128M';
90 $limit = trim(str_replace('-1', '128M', $limit));
91 $unit = strtoupper(substr($limit, -1));
92 $limit = (int) $limit;
93 $multiplier = self::LENGTH_MULTIPLIER[$unit] ?? 1;
94 $limit *= $multiplier;
95 $this->maxLength = intdiv($limit, 4);
96 }
97
98 public function setMaxLength(int $maxLength): self
99 {
100 $this->maxLength = $maxLength;
101
102 return $this;
103 }
104
105 /**
106 * Can the current IReader read the file?
107 */
108 public function canRead(string $filename): bool
109 {
110 $data = null;
111 if (File::testFileNoThrow($filename)) {
112 $data = $this->gzfileGetContents($filename);
113 if (!str_contains($data, self::NAMESPACE_GNM)) {
114 $data = '';
115 }
116 }
117
118 return !empty($data);
119 }
120
121 private static function matchXml(XMLReader $xml, string $expectedLocalName): bool
122 {
123 return $xml->namespaceURI === self::NAMESPACE_GNM
124 && $xml->localName === $expectedLocalName
125 && $xml->nodeType === XMLReader::ELEMENT;
126 }
127
128 /**
129 * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
130 *
131 * @return string[]
132 */
133 public function listWorksheetNames(string $filename): array
134 {
135 File::assertFile($filename);
136 if (!$this->canRead($filename)) {
137 throw new Exception($filename . ' is an invalid Gnumeric file.');
138 }
139
140 $xml = new XMLReader();
141 $contents = $this->gzfileGetContents($filename);
142 $xml->xml($contents);
143 $xml->setParserProperty(2, true);
144
145 $worksheetNames = [];
146 while ($xml->read()) {
147 if (self::matchXml($xml, 'SheetName')) {
148 $xml->read(); // Move onto the value node
149 $worksheetNames[] = (string) $xml->value;
150 } elseif (self::matchXml($xml, 'Sheets')) {
151 // break out of the loop once we've got our sheet names rather than parse the entire file
152 break;
153 }
154 }
155
156 return $worksheetNames;
157 }
158
159 /**
160 * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
161 *
162 * @return array<int, array{worksheetName: string, lastColumnLetter: string, lastColumnIndex: int, totalRows: int, totalColumns: int, sheetState: string}>
163 */
164 public function listWorksheetInfo(string $filename): array
165 {
166 File::assertFile($filename);
167 if (!$this->canRead($filename)) {
168 throw new Exception($filename . ' is an invalid Gnumeric file.');
169 }
170
171 $xml = new XMLReader();
172 $contents = $this->gzfileGetContents($filename);
173 $xml->xml($contents);
174 $xml->setParserProperty(2, true);
175
176 $worksheetInfo = [];
177 while ($xml->read()) {
178 if (self::matchXml($xml, 'Sheet')) {
179 $tmpInfo = [
180 'worksheetName' => '',
181 'lastColumnLetter' => 'A',
182 'lastColumnIndex' => 0,
183 'totalRows' => 0,
184 'totalColumns' => 0,
185 'sheetState' => Worksheet::SHEETSTATE_VISIBLE,
186 ];
187 $visibility = $xml->getAttribute('Visibility');
188 if ((string) $visibility === self::GNM_SHEET_VISIBILITY_HIDDEN) {
189 $tmpInfo['sheetState'] = Worksheet::SHEETSTATE_HIDDEN;
190 }
191
192 while ($xml->read()) {
193 if (self::matchXml($xml, 'Name')) {
194 $xml->read(); // Move onto the value node
195 $tmpInfo['worksheetName'] = (string) $xml->value;
196 } elseif (self::matchXml($xml, 'MaxCol')) {
197 $xml->read(); // Move onto the value node
198 $tmpInfo['lastColumnIndex'] = (int) $xml->value;
199 $tmpInfo['totalColumns'] = (int) $xml->value + 1;
200 } elseif (self::matchXml($xml, 'MaxRow')) {
201 $xml->read(); // Move onto the value node
202 $tmpInfo['totalRows'] = (int) $xml->value + 1;
203
204 break;
205 }
206 }
207 $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1, true);
208 $worksheetInfo[] = $tmpInfo;
209 }
210 }
211
212 return $worksheetInfo;
213 }
214
215 private function gzfileGetContents(string $filename): string
216 {
217 $data = '';
218 $contents = @file_get_contents($filename);
219 if ($contents !== false) {
220 if (str_starts_with($contents, "\x1f\x8b")) {
221 // Check if gzlib functions are available
222 if (function_exists('gzdecode')) {
223 $contents = @gzdecode($contents, $this->maxLength);
224 if ($contents !== false) {
225 $data = $contents;
226 }
227 }
228 } else {
229 $data = $contents;
230 }
231 }
232 if ($data !== '') {
233 $data = $this->getSecurityScannerOrThrow()->scan($data);
234 }
235
236 return $data;
237 }
238
239 /**
240 * @return mixed[]
241 *
242 * @internal
243 */
244 public static function gnumericMappings(): array
245 {
246 return array_merge(self::MAPPINGS, Styles::MAPPINGS);
247 }
248
249 private function processComments(SimpleXMLElement $sheet): void
250 {
251 if ((!$this->readDataOnly) && (isset($sheet->Objects))) {
252 foreach ($sheet->Objects->children(self::NAMESPACE_GNM) as $key => $comment) {
253 $commentAttributes = $comment->attributes();
254 // Only comment objects are handled at the moment
255 if ($commentAttributes && $commentAttributes->Text) {
256 $this->spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound)
257 ->setAuthor((string) $commentAttributes->Author)
258 ->setText($this->parseRichText((string) $commentAttributes->Text));
259 }
260 }
261 }
262 }
263
264 /**
265 * @param mixed $value
266 */
267 private static function testSimpleXml($value): SimpleXMLElement
268 {
269 return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><root></root>');
270 }
271
272 /**
273 * Loads Spreadsheet from file.
274 */
275 protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
276 {
277 $spreadsheet = $this->newSpreadsheet();
278 $spreadsheet->setValueBinder($this->valueBinder);
279 $spreadsheet->removeSheetByIndex(0);
280
281 // Load into this instance
282 return $this->loadIntoExisting($filename, $spreadsheet);
283 }
284
285 /**
286 * Loads from file into Spreadsheet instance.
287 */
288 public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet
289 {
290 $this->spreadsheet = $spreadsheet;
291 File::assertFile($filename);
292 if (!$this->canRead($filename)) {
293 throw new Exception($filename . ' is an invalid Gnumeric file.');
294 }
295
296 $gFileData = $this->gzfileGetContents($filename);
297
298 $securityScanner = $this->getSecurityScannerOrThrow();
299 $xml2 = simplexml_load_string($securityScanner->scan($gFileData));
300 $xml = self::testSimpleXml($xml2);
301
302 $gnmXML = $xml->children(self::NAMESPACE_GNM);
303 (new Properties($this->spreadsheet))->readProperties($xml, $gnmXML);
304
305 $worksheetID = 0;
306 $sheetCreated = false;
307 foreach ($gnmXML->Sheets->Sheet as $sheetOrNull) {
308 $sheet = self::testSimpleXml($sheetOrNull);
309 $worksheetName = (string) $sheet->Name;
310 if (is_array($this->loadSheetsOnly) && !in_array($worksheetName, $this->loadSheetsOnly, true)) {
311 continue;
312 }
313
314 $maxRow = $maxCol = 0;
315
316 // Create new Worksheet
317 $this->spreadsheet->createSheet();
318 $sheetCreated = true;
319 $this->spreadsheet->setActiveSheetIndex($worksheetID);
320 // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
321 // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
322 // name in line with the formula, not the reverse
323 $this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
324
325 $visibility = $sheet->attributes()['Visibility'] ?? self::GNM_SHEET_VISIBILITY_VISIBLE;
326 if ((string) $visibility !== self::GNM_SHEET_VISIBILITY_VISIBLE) {
327 $this->spreadsheet->getActiveSheet()->setSheetState(Worksheet::SHEETSTATE_HIDDEN);
328 }
329
330 if (!$this->readDataOnly) {
331 (new PageSetup($this->spreadsheet))
332 ->printInformation($sheet)
333 ->sheetMargins($sheet);
334 }
335
336 foreach ($sheet->Cells->Cell as $cellOrNull) {
337 $cell = self::testSimpleXml($cellOrNull);
338 $cellAttributes = self::testSimpleXml($cell->attributes());
339 $row = (int) $cellAttributes->Row + 1;
340 $column = (int) $cellAttributes->Col;
341
342 $maxRow = max($maxRow, $row);
343 $maxCol = max($maxCol, $column);
344
345 $column = Coordinate::stringFromColumnIndex($column + 1);
346
347 // Read cell?
348 if (!$this->readFilter->readCell($column, $row, $worksheetName)) {
349 continue;
350 }
351
352 $this->loadCell($cell, $worksheetName, $cellAttributes, $column, $row);
353 }
354
355 if ($sheet->Styles !== null) {
356 (new Styles($this->spreadsheet, $this->readDataOnly))->read($sheet, $maxRow, $maxCol);
357 }
358
359 $this->processComments($sheet);
360 $this->processColumnWidths($sheet, $maxCol);
361 $this->processRowHeights($sheet, $maxRow);
362 $this->processMergedCells($sheet);
363 $this->processAutofilter($sheet);
364
365 $this->setSelectedCells($sheet);
366 ++$worksheetID;
367 }
368 if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
369 $this->spreadsheet->createSheet();
370 }
371
372 $this->processDefinedNames($gnmXML);
373
374 $this->setSelectedSheet($gnmXML);
375
376 // Return
377 return $this->spreadsheet;
378 }
379
380 private function setSelectedSheet(SimpleXMLElement $gnmXML): void
381 {
382 if (isset($gnmXML->UIData)) {
383 $attributes = self::testSimpleXml($gnmXML->UIData->attributes());
384 $selectedSheet = (int) $attributes['SelectedTab'];
385 $this->spreadsheet->setActiveSheetIndex($selectedSheet);
386 }
387 }
388
389 private function setSelectedCells(?SimpleXMLElement $sheet): void
390 {
391 if ($sheet !== null && isset($sheet->Selections)) {
392 foreach ($sheet->Selections as $selection) {
393 $startCol = (int) ($selection->StartCol ?? 0);
394 $startRow = (int) ($selection->StartRow ?? 0) + 1;
395 $endCol = (int) ($selection->EndCol ?? $startCol);
396 $endRow = (int) ($selection->endRow ?? 0) + 1;
397
398 $startColumn = Coordinate::stringFromColumnIndex($startCol + 1);
399 $endColumn = Coordinate::stringFromColumnIndex($endCol + 1);
400
401 $startCell = "{$startColumn}{$startRow}";
402 $endCell = "{$endColumn}{$endRow}";
403 $selectedRange = $startCell . (($endCell !== $startCell) ? ':' . $endCell : '');
404 $this->spreadsheet->getActiveSheet()->setSelectedCell($selectedRange);
405
406 break;
407 }
408 }
409 }
410
411 private function processMergedCells(?SimpleXMLElement $sheet): void
412 {
413 // Handle Merged Cells in this worksheet
414 if ($sheet !== null && isset($sheet->MergedRegions)) {
415 foreach ($sheet->MergedRegions->Merge as $mergeCells) {
416 if (str_contains((string) $mergeCells, ':')) {
417 $this->spreadsheet->getActiveSheet()->mergeCells($mergeCells, Worksheet::MERGE_CELL_CONTENT_HIDE);
418 }
419 }
420 }
421 }
422
423 private function processAutofilter(?SimpleXMLElement $sheet): void
424 {
425 if ($sheet !== null && isset($sheet->Filters)) {
426 foreach ($sheet->Filters->Filter as $autofilter) {
427 $attributes = $autofilter->attributes();
428 if (isset($attributes['Area'])) {
429 $this->spreadsheet->getActiveSheet()->setAutoFilter((string) $attributes['Area']);
430 }
431 }
432 }
433 }
434
435 private function setColumnWidth(int $whichColumn, float $defaultWidth): void
436 {
437 $this->spreadsheet->getActiveSheet()
438 ->getColumnDimension(
439 Coordinate::stringFromColumnIndex($whichColumn + 1)
440 )
441 ->setWidth($defaultWidth);
442 }
443
444 private function setColumnInvisible(int $whichColumn): void
445 {
446 $this->spreadsheet->getActiveSheet()
447 ->getColumnDimension(
448 Coordinate::stringFromColumnIndex($whichColumn + 1)
449 )
450 ->setVisible(false);
451 }
452
453 private function processColumnLoop(int $whichColumn, int $maxCol, ?SimpleXMLElement $columnOverride, float $defaultWidth): int
454 {
455 $columnOverride = self::testSimpleXml($columnOverride);
456 $columnAttributes = self::testSimpleXml($columnOverride->attributes());
457 $column = $columnAttributes['No'];
458 $columnWidth = ((float) $columnAttributes['Unit']) / 5.4;
459 $hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1');
460 $columnCount = (int) ($columnAttributes['Count'] ?? 1);
461 while ($whichColumn < $column) {
462 $this->setColumnWidth($whichColumn, $defaultWidth);
463 ++$whichColumn;
464 }
465 while (($whichColumn < ($column + $columnCount)) && ($whichColumn <= $maxCol)) {
466 $this->setColumnWidth($whichColumn, $columnWidth);
467 if ($hidden) {
468 $this->setColumnInvisible($whichColumn);
469 }
470 ++$whichColumn;
471 }
472
473 return $whichColumn;
474 }
475
476 private function processColumnWidths(?SimpleXMLElement $sheet, int $maxCol): void
477 {
478 if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Cols))) {
479 // Column Widths
480 $defaultWidth = 0;
481 $columnAttributes = $sheet->Cols->attributes();
482 if ($columnAttributes !== null) {
483 $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4;
484 }
485 $whichColumn = 0;
486 foreach ($sheet->Cols->ColInfo as $columnOverride) {
487 $whichColumn = $this->processColumnLoop($whichColumn, $maxCol, $columnOverride, $defaultWidth);
488 }
489 while ($whichColumn <= $maxCol) {
490 $this->setColumnWidth($whichColumn, $defaultWidth);
491 ++$whichColumn;
492 }
493 }
494 }
495
496 private function setRowHeight(int $whichRow, float $defaultHeight): void
497 {
498 $this->spreadsheet
499 ->getActiveSheet()
500 ->getRowDimension($whichRow)
501 ->setRowHeight($defaultHeight);
502 }
503
504 private function setRowInvisible(int $whichRow): void
505 {
506 $this->spreadsheet
507 ->getActiveSheet()
508 ->getRowDimension($whichRow)
509 ->setVisible(false);
510 }
511
512 private function processRowLoop(int $whichRow, int $maxRow, ?SimpleXMLElement $rowOverride, float $defaultHeight): int
513 {
514 $rowOverride = self::testSimpleXml($rowOverride);
515 $rowAttributes = self::testSimpleXml($rowOverride->attributes());
516 $row = $rowAttributes['No'];
517 $rowHeight = (float) $rowAttributes['Unit'];
518 $hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1');
519 $rowCount = (int) ($rowAttributes['Count'] ?? 1);
520 while ($whichRow < $row) {
521 ++$whichRow;
522 $this->setRowHeight($whichRow, $defaultHeight);
523 }
524 while (($whichRow < ($row + $rowCount)) && ($whichRow < $maxRow)) {
525 ++$whichRow;
526 $this->setRowHeight($whichRow, $rowHeight);
527 if ($hidden) {
528 $this->setRowInvisible($whichRow);
529 }
530 }
531
532 return $whichRow;
533 }
534
535 private function processRowHeights(?SimpleXMLElement $sheet, int $maxRow): void
536 {
537 if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Rows))) {
538 // Row Heights
539 $defaultHeight = 0;
540 $rowAttributes = $sheet->Rows->attributes();
541 if ($rowAttributes !== null) {
542 $defaultHeight = (float) $rowAttributes['DefaultSizePts'];
543 }
544 $whichRow = 0;
545
546 foreach ($sheet->Rows->RowInfo as $rowOverride) {
547 $whichRow = $this->processRowLoop($whichRow, $maxRow, $rowOverride, $defaultHeight);
548 }
549 // never executed, I can't figure out any circumstances
550 // under which it would be executed, and, even if
551 // such exist, I'm not convinced this is needed.
552 //while ($whichRow < $maxRow) {
553 // ++$whichRow;
554 // $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow)->setRowHeight($defaultHeight);
555 //}
556 }
557 }
558
559 private function processDefinedNames(?SimpleXMLElement $gnmXML): void
560 {
561 // Loop through definedNames (global named ranges)
562 if ($gnmXML !== null && isset($gnmXML->Names)) {
563 foreach ($gnmXML->Names->Name as $definedName) {
564 $name = (string) $definedName->name;
565 $value = (string) $definedName->value;
566 if (stripos($value, '#REF!') !== false || empty($value)) {
567 continue;
568 }
569
570 $value = str_replace("\\'", "''", $value);
571 [$worksheetName] = Worksheet::extractSheetTitle($value, true, true);
572 $worksheet = $this->spreadsheet->getSheetByName($worksheetName);
573 // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet
574 if ($worksheet !== null) {
575 $this->spreadsheet->addDefinedName(DefinedName::createInstance($name, $worksheet, $value));
576 }
577 }
578 }
579 }
580
581 private function parseRichText(string $is): RichText
582 {
583 $value = new RichText();
584 $value->createText($is);
585
586 return $value;
587 }
588
589 private function loadCell(
590 SimpleXMLElement $cell,
591 string $worksheetName,
592 SimpleXMLElement $cellAttributes,
593 string $column,
594 int $row
595 ): void {
596 $ValueType = $cellAttributes->ValueType;
597 $ExprID = (string) $cellAttributes->ExprID;
598 $rows = (int) ($cellAttributes->Rows ?? 0);
599 $cols = (int) ($cellAttributes->Cols ?? 0);
600 $type = DataType::TYPE_FORMULA;
601 $isArrayFormula = ($rows > 0 && $cols > 0);
602 $arrayFormulaRange = $isArrayFormula ? $this->getArrayFormulaRange($column, $row, $cols, $rows) : null;
603 if ($ExprID > '') {
604 if (((string) $cell) > '') {
605 // Formula
606 $this->expressions[$ExprID] = [
607 'column' => (int) $cellAttributes->Col,
608 'row' => (int) $cellAttributes->Row,
609 'formula' => (string) $cell,
610 ];
611 } else {
612 // Shared Formula
613 $expression = $this->expressions[$ExprID];
614
615 $cell = $this->referenceHelper->updateFormulaReferences(
616 $expression['formula'],
617 'A1',
618 $cellAttributes->Col - $expression['column'],
619 $cellAttributes->Row - $expression['row'],
620 $worksheetName
621 );
622 }
623 $type = DataType::TYPE_FORMULA;
624 } elseif ($isArrayFormula === false) {
625 $vtype = (string) $ValueType;
626 if (array_key_exists($vtype, self::MAPPINGS['dataType'])) {
627 $type = self::MAPPINGS['dataType'][$vtype];
628 }
629 if ($vtype === '20') { // Boolean
630 $cell = $cell == 'TRUE';
631 }
632 }
633
634 $this->spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit((string) $cell, $type);
635 if ($arrayFormulaRange === null) {
636 $this->spreadsheet->getActiveSheet()->getCell($column . $row)->setFormulaAttributes(null);
637 } else {
638 $this->spreadsheet->getActiveSheet()->getCell($column . $row)->setFormulaAttributes(['t' => 'array', 'ref' => $arrayFormulaRange]);
639 }
640 if (isset($cellAttributes->ValueFormat)) {
641 $this->spreadsheet->getActiveSheet()->getCell($column . $row)
642 ->getStyle()->getNumberFormat()
643 ->setFormatCode((string) $cellAttributes->ValueFormat);
644 }
645 }
646
647 private function getArrayFormulaRange(string $column, int $row, int $cols, int $rows): string
648 {
649 $arrayFormulaRange = $column . $row;
650 $arrayFormulaRange .= ':'
651 . Coordinate::stringFromColumnIndex(
652 Coordinate::columnIndexFromString($column)
653 + $cols - 1
654 )
655 . (string) ($row + $rows - 1);
656
657 return $arrayFormulaRange;
658 }
659 }
660