Passed
Push — develop ( 39b573...699da0 )
by Adrien
27:28
created

Ods::listWorksheetNames()   B

Complexity

Conditions 9
Paths 11

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 9.0058

Importance

Changes 0
Metric Value
cc 9
eloc 25
nc 11
nop 1
dl 0
loc 43
ccs 23
cts 24
cp 0.9583
crap 9.0058
rs 8.0555
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use DateTime;
6
use DateTimeZone;
7
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
8
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
9
use PhpOffice\PhpSpreadsheet\Cell\DataType;
10
use PhpOffice\PhpSpreadsheet\Document\Properties;
11
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
12
use PhpOffice\PhpSpreadsheet\RichText\RichText;
13
use PhpOffice\PhpSpreadsheet\Settings;
14
use PhpOffice\PhpSpreadsheet\Shared\Date;
15
use PhpOffice\PhpSpreadsheet\Shared\File;
16
use PhpOffice\PhpSpreadsheet\Spreadsheet;
17
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
18
use XMLReader;
19
use ZipArchive;
20
21
class Ods extends BaseReader
22
{
23
    /**
24
     * Create a new Ods Reader instance.
25
     */
26 16
    public function __construct()
27
    {
28 16
        $this->readFilter = new DefaultReadFilter();
29 16
        $this->securityScanner = XmlScanner::getInstance($this);
30 16
    }
31
32
    /**
33
     * Can the current IReader read the file?
34
     *
35
     * @param string $pFilename
36
     *
37
     * @throws Exception
38
     *
39
     * @return bool
40
     */
41 3
    public function canRead($pFilename)
42
    {
43 3
        File::assertFile($pFilename);
44
45 3
        $mimeType = 'UNKNOWN';
46
47
        // Load file
48
49 3
        $zip = new ZipArchive();
50 3
        if ($zip->open($pFilename) === true) {
51
            // check if it is an OOXML archive
52 3
            $stat = $zip->statName('mimetype');
53 3
            if ($stat && ($stat['size'] <= 255)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stat of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
54 3
                $mimeType = $zip->getFromName($stat['name']);
55
            } elseif ($stat = $zip->statName('META-INF/manifest.xml')) {
0 ignored issues
show
Unused Code introduced by
The assignment to $stat is dead and can be removed.
Loading history...
56
                $xml = simplexml_load_string(
57
                    $this->securityScanner->scan($zip->getFromName('META-INF/manifest.xml')),
58
                    'SimpleXMLElement',
59
                    Settings::getLibXmlLoaderOptions()
60
                );
61
                $namespacesContent = $xml->getNamespaces(true);
62
                if (isset($namespacesContent['manifest'])) {
63
                    $manifest = $xml->children($namespacesContent['manifest']);
64
                    foreach ($manifest as $manifestDataSet) {
65
                        $manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']);
66
                        if ($manifestAttributes->{'full-path'} == '/') {
67
                            $mimeType = (string) $manifestAttributes->{'media-type'};
68
69
                            break;
70
                        }
71
                    }
72
                }
73
            }
74
75 3
            $zip->close();
76
77 3
            return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet';
78
        }
79
80
        return false;
81
    }
82
83
    /**
84
     * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
85
     *
86
     * @param string $pFilename
87
     *
88
     * @throws Exception
89
     *
90
     * @return string[]
91
     */
92 1
    public function listWorksheetNames($pFilename)
93
    {
94 1
        File::assertFile($pFilename);
95
96 1
        $zip = new ZipArchive();
97 1
        if (!$zip->open($pFilename)) {
98
            throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.');
99
        }
100
101 1
        $worksheetNames = [];
102
103 1
        $xml = new XMLReader();
104 1
        $xml->xml(
105 1
            $this->securityScanner->scanFile('zip://' . realpath($pFilename) . '#content.xml'),
106 1
            null,
107 1
            Settings::getLibXmlLoaderOptions()
108
        );
109 1
        $xml->setParserProperty(2, true);
110
111
        // Step into the first level of content of the XML
112 1
        $xml->read();
113 1
        while ($xml->read()) {
114
            // Quickly jump through to the office:body node
115 1
            while ($xml->name !== 'office:body') {
116 1
                if ($xml->isEmptyElement) {
117 1
                    $xml->read();
118
                } else {
119 1
                    $xml->next();
120
                }
121
            }
122
            // Now read each node until we find our first table:table node
123 1
            while ($xml->read()) {
124 1
                if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
125
                    // Loop through each table:table node reading the table:name attribute for each worksheet name
126
                    do {
127 1
                        $worksheetNames[] = $xml->getAttribute('table:name');
128 1
                        $xml->next();
129 1
                    } while ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT);
130
                }
131
            }
132
        }
133
134 1
        return $worksheetNames;
135
    }
136
137
    /**
138
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
139
     *
140
     * @param string $pFilename
141
     *
142
     * @throws Exception
143
     *
144
     * @return array
145
     */
146
    public function listWorksheetInfo($pFilename)
147
    {
148
        File::assertFile($pFilename);
149
150
        $worksheetInfo = [];
151
152
        $zip = new ZipArchive();
153
        if (!$zip->open($pFilename)) {
154
            throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.');
155
        }
156
157
        $xml = new XMLReader();
158
        $xml->xml(
159
            $this->securityScanner->scanFile('zip://' . realpath($pFilename) . '#content.xml'),
160
            null,
161
            Settings::getLibXmlLoaderOptions()
162
        );
163
        $xml->setParserProperty(2, true);
164
165
        // Step into the first level of content of the XML
166
        $xml->read();
167
        while ($xml->read()) {
168
            // Quickly jump through to the office:body node
169
            while ($xml->name !== 'office:body') {
170
                if ($xml->isEmptyElement) {
171
                    $xml->read();
172
                } else {
173
                    $xml->next();
174
                }
175
            }
176
            // Now read each node until we find our first table:table node
177
            while ($xml->read()) {
178
                if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
179
                    $worksheetNames[] = $xml->getAttribute('table:name');
180
181
                    $tmpInfo = [
182
                        'worksheetName' => $xml->getAttribute('table:name'),
183
                        'lastColumnLetter' => 'A',
184
                        'lastColumnIndex' => 0,
185
                        'totalRows' => 0,
186
                        'totalColumns' => 0,
187
                    ];
188
189
                    // Loop through each child node of the table:table element reading
190
                    $currCells = 0;
191
                    do {
192
                        $xml->read();
193
                        if ($xml->name == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) {
194
                            $rowspan = $xml->getAttribute('table:number-rows-repeated');
195
                            $rowspan = empty($rowspan) ? 1 : $rowspan;
196
                            $tmpInfo['totalRows'] += $rowspan;
197
                            $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
198
                            $currCells = 0;
199
                            // Step into the row
200
                            $xml->read();
201
                            do {
202
                                if ($xml->name == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
203
                                    if (!$xml->isEmptyElement) {
204
                                        ++$currCells;
205
                                        $xml->next();
206
                                    } else {
207
                                        $xml->read();
208
                                    }
209
                                } elseif ($xml->name == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
210
                                    $mergeSize = $xml->getAttribute('table:number-columns-repeated');
211
                                    $currCells += (int) $mergeSize;
212
                                    $xml->read();
213
                                } else {
214
                                    $xml->read();
215
                                }
216
                            } while ($xml->name != 'table:table-row');
217
                        }
218
                    } while ($xml->name != 'table:table');
219
220
                    $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
221
                    $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
222
                    $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
223
                    $worksheetInfo[] = $tmpInfo;
224
                }
225
            }
226
        }
227
228
        return $worksheetInfo;
229
    }
230
231
    /**
232
     * Loads PhpSpreadsheet from file.
233
     *
234
     * @param string $pFilename
235
     *
236
     * @throws Exception
237
     *
238
     * @return Spreadsheet
239
     */
240 6
    public function load($pFilename)
241
    {
242
        // Create new Spreadsheet
243 6
        $spreadsheet = new Spreadsheet();
244
245
        // Load into this instance
246 6
        return $this->loadIntoExisting($pFilename, $spreadsheet);
247
    }
248
249
    /**
250
     * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
251
     *
252
     * @param string $pFilename
253
     * @param Spreadsheet $spreadsheet
254
     *
255
     * @throws Exception
256
     *
257
     * @return Spreadsheet
258
     */
259 12
    public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
260
    {
261 12
        File::assertFile($pFilename);
262
263 12
        $timezoneObj = new DateTimeZone('Europe/London');
264 12
        $GMT = new \DateTimeZone('UTC');
265
266 12
        $zip = new ZipArchive();
267 12
        if (!$zip->open($pFilename)) {
268
            throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.');
269
        }
270
271
        // Meta
272
273 12
        $xml = simplexml_load_string(
274 12
            $this->securityScanner->scan($zip->getFromName('meta.xml')),
275 12
            'SimpleXMLElement',
276 12
            Settings::getLibXmlLoaderOptions()
277
        );
278 12
        $namespacesMeta = $xml->getNamespaces(true);
279
280 12
        $docProps = $spreadsheet->getProperties();
281 12
        $officeProperty = $xml->children($namespacesMeta['office']);
282 12
        foreach ($officeProperty as $officePropertyData) {
283 12
            $officePropertyDC = [];
284 12
            if (isset($namespacesMeta['dc'])) {
285 12
                $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']);
286
            }
287 12
            foreach ($officePropertyDC as $propertyName => $propertyValue) {
288 12
                $propertyValue = (string) $propertyValue;
289 12
                switch ($propertyName) {
290 12
                    case 'title':
291 10
                        $docProps->setTitle($propertyValue);
292
293 10
                        break;
294 12
                    case 'subject':
295 10
                        $docProps->setSubject($propertyValue);
296
297 10
                        break;
298 12
                    case 'creator':
299 5
                        $docProps->setCreator($propertyValue);
300 5
                        $docProps->setLastModifiedBy($propertyValue);
301
302 5
                        break;
303 12
                    case 'date':
304 12
                        $creationDate = strtotime($propertyValue);
305 12
                        $docProps->setCreated($creationDate);
306 12
                        $docProps->setModified($creationDate);
307
308 12
                        break;
309 10
                    case 'description':
310 10
                        $docProps->setDescription($propertyValue);
311
312 12
                        break;
313
                }
314
            }
315 12
            $officePropertyMeta = [];
316 12
            if (isset($namespacesMeta['dc'])) {
317 12
                $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
318
            }
319 12
            foreach ($officePropertyMeta as $propertyName => $propertyValue) {
320 12
                $propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']);
321 12
                $propertyValue = (string) $propertyValue;
322 12
                switch ($propertyName) {
323 12
                    case 'initial-creator':
324 10
                        $docProps->setCreator($propertyValue);
325
326 10
                        break;
327 12
                    case 'keyword':
328 10
                        $docProps->setKeywords($propertyValue);
329
330 10
                        break;
331 12
                    case 'creation-date':
332 12
                        $creationDate = strtotime($propertyValue);
333 12
                        $docProps->setCreated($creationDate);
334
335 12
                        break;
336 12
                    case 'user-defined':
337 10
                        $propertyValueType = Properties::PROPERTY_TYPE_STRING;
338 10
                        foreach ($propertyValueAttributes as $key => $value) {
339 10
                            if ($key == 'name') {
340 10
                                $propertyValueName = (string) $value;
341
                            } elseif ($key == 'value-type') {
342
                                switch ($value) {
343
                                    case 'date':
344
                                        $propertyValue = Properties::convertProperty($propertyValue, 'date');
345
                                        $propertyValueType = Properties::PROPERTY_TYPE_DATE;
346
347
                                        break;
348
                                    case 'boolean':
349
                                        $propertyValue = Properties::convertProperty($propertyValue, 'bool');
350
                                        $propertyValueType = Properties::PROPERTY_TYPE_BOOLEAN;
351
352
                                        break;
353
                                    case 'float':
354
                                        $propertyValue = Properties::convertProperty($propertyValue, 'r4');
355
                                        $propertyValueType = Properties::PROPERTY_TYPE_FLOAT;
356
357
                                        break;
358
                                    default:
359 10
                                        $propertyValueType = Properties::PROPERTY_TYPE_STRING;
360
                                }
361
                            }
362
                        }
363 10
                        $docProps->setCustomProperty($propertyValueName, $propertyValue, $propertyValueType);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $propertyValueName does not seem to be defined for all execution paths leading up to this point.
Loading history...
364
365 12
                        break;
366
                }
367
            }
368
        }
369
370
        // Content
371
372 12
        $dom = new \DOMDocument('1.01', 'UTF-8');
373 12
        $dom->loadXML(
374 12
            $this->securityScanner->scan($zip->getFromName('content.xml')),
375 12
            Settings::getLibXmlLoaderOptions()
376
        );
377
378 12
        $officeNs = $dom->lookupNamespaceUri('office');
379 12
        $tableNs = $dom->lookupNamespaceUri('table');
380 12
        $textNs = $dom->lookupNamespaceUri('text');
381 12
        $xlinkNs = $dom->lookupNamespaceUri('xlink');
382
383 12
        $spreadsheets = $dom->getElementsByTagNameNS($officeNs, 'body')
384 12
            ->item(0)
385 12
            ->getElementsByTagNameNS($officeNs, 'spreadsheet');
386
387 12
        foreach ($spreadsheets as $workbookData) {
388
            /** @var \DOMElement $workbookData */
389 12
            $tables = $workbookData->getElementsByTagNameNS($tableNs, 'table');
390
391 12
            $worksheetID = 0;
392 12
            foreach ($tables as $worksheetDataSet) {
393
                /** @var \DOMElement $worksheetDataSet */
394 12
                $worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name');
395
396
                // Check loadSheetsOnly
397 12
                if (isset($this->loadSheetsOnly)
398 12
                    && $worksheetName
399 12
                    && !in_array($worksheetName, $this->loadSheetsOnly)) {
400
                    continue;
401
                }
402
403
                // Create sheet
404 12
                if ($worksheetID > 0) {
405 7
                    $spreadsheet->createSheet(); // First sheet is added by default
406
                }
407 12
                $spreadsheet->setActiveSheetIndex($worksheetID);
408
409 12
                if ($worksheetName) {
410
                    // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
411
                    // formula cells... during the load, all formulae should be correct, and we're simply
412
                    // bringing the worksheet name in line with the formula, not the reverse
413 12
                    $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
414
                }
415
416
                // Go through every child of table element
417 12
                $rowID = 1;
418 12
                foreach ($worksheetDataSet->childNodes as $childNode) {
419
                    /** @var \DOMElement $childNode */
420
421
                    // Filter elements which are not under the "table" ns
422 12
                    if ($childNode->namespaceURI != $tableNs) {
423 10
                        continue;
424
                    }
425
426 12
                    $key = $childNode->nodeName;
427
428
                    // Remove ns from node name
429 12
                    if (strpos($key, ':') !== false) {
430 12
                        $keyChunks = explode(':', $key);
431 12
                        $key = array_pop($keyChunks);
432
                    }
433
434 12
                    switch ($key) {
435 12
                        case 'table-header-rows':
436
                            /// TODO :: Figure this out. This is only a partial implementation I guess.
437
                            //          ($rowData it's not used at all and I'm not sure that PHPExcel
438
                            //          has an API for this)
439
440
//                            foreach ($rowData as $keyRowData => $cellData) {
441
//                                $rowData = $cellData;
442
//                                break;
443
//                            }
444
                            break;
445 12
                        case 'table-row':
446 12
                            if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) {
447 7
                                $rowRepeats = $childNode->getAttributeNS($tableNs, 'number-rows-repeated');
448
                            } else {
449 12
                                $rowRepeats = 1;
450
                            }
451
452 12
                            $columnID = 'A';
453 12
                            foreach ($childNode->childNodes as $key => $cellData) {
454
                                // @var \DOMElement $cellData
455
456 12
                                if ($this->getReadFilter() !== null) {
457 12
                                    if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
458 1
                                        ++$columnID;
459
460 1
                                        continue;
461
                                    }
462
                                }
463
464
                                // Initialize variables
465 12
                                $formatting = $hyperlink = null;
466 12
                                $hasCalculatedValue = false;
467 12
                                $cellDataFormula = '';
468
469 12
                                if ($cellData->hasAttributeNS($tableNs, 'formula')) {
470 5
                                    $cellDataFormula = $cellData->getAttributeNS($tableNs, 'formula');
471 5
                                    $hasCalculatedValue = true;
472
                                }
473
474
                                // Annotations
475 12
                                $annotation = $cellData->getElementsByTagNameNS($officeNs, 'annotation');
476
477 12
                                if ($annotation->length > 0) {
478 6
                                    $textNode = $annotation->item(0)->getElementsByTagNameNS($textNs, 'p');
479
480 6
                                    if ($textNode->length > 0) {
481 6
                                        $text = $this->scanElementForText($textNode->item(0));
482
483 6
                                        $spreadsheet->getActiveSheet()
484 6
                                            ->getComment($columnID . $rowID)
485 6
                                            ->setText($this->parseRichText($text));
486
//                                                                    ->setAuthor( $author )
487
                                    }
488
                                }
489
490
                                // Content
491
492
                                /** @var \DOMElement[] $paragraphs */
493 12
                                $paragraphs = [];
494
495 12
                                foreach ($cellData->childNodes as $item) {
496
                                    /** @var \DOMElement $item */
497
498
                                    // Filter text:p elements
499 12
                                    if ($item->nodeName == 'text:p') {
500 12
                                        $paragraphs[] = $item;
501
                                    }
502
                                }
503
504 12
                                if (count($paragraphs) > 0) {
505
                                    // Consolidate if there are multiple p records (maybe with spans as well)
506 12
                                    $dataArray = [];
507
508
                                    // Text can have multiple text:p and within those, multiple text:span.
509
                                    // text:p newlines, but text:span does not.
510
                                    // Also, here we assume there is no text data is span fields are specified, since
511
                                    // we have no way of knowing proper positioning anyway.
512
513 12
                                    foreach ($paragraphs as $pData) {
514 12
                                        $dataArray[] = $this->scanElementForText($pData);
515
                                    }
516 12
                                    $allCellDataText = implode($dataArray, "\n");
0 ignored issues
show
Unused Code introduced by
The call to implode() has too many arguments starting with ' '. ( Ignorable by Annotation )

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

516
                                    $allCellDataText = /** @scrutinizer ignore-call */ implode($dataArray, "\n");

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
517
518 12
                                    $type = $cellData->getAttributeNS($officeNs, 'value-type');
519
520 12
                                    switch ($type) {
521 12
                                        case 'string':
522 11
                                            $type = DataType::TYPE_STRING;
523 11
                                            $dataValue = $allCellDataText;
524
525 11
                                            foreach ($paragraphs as $paragraph) {
526 11
                                                $link = $paragraph->getElementsByTagNameNS($textNs, 'a');
527 11
                                                if ($link->length > 0) {
528 11
                                                    $hyperlink = $link->item(0)->getAttributeNS($xlinkNs, 'href');
529
                                                }
530
                                            }
531
532 11
                                            break;
533 9
                                        case 'boolean':
534 5
                                            $type = DataType::TYPE_BOOL;
535 5
                                            $dataValue = ($allCellDataText == 'TRUE') ? true : false;
536
537 5
                                            break;
538 9
                                        case 'percentage':
539 3
                                            $type = DataType::TYPE_NUMERIC;
540 3
                                            $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
541
542 3
                                            if (floor($dataValue) == $dataValue) {
543
                                                $dataValue = (int) $dataValue;
544
                                            }
545 3
                                            $formatting = NumberFormat::FORMAT_PERCENTAGE_00;
546
547 3
                                            break;
548 9
                                        case 'currency':
549 3
                                            $type = DataType::TYPE_NUMERIC;
550 3
                                            $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
551
552 3
                                            if (floor($dataValue) == $dataValue) {
553 3
                                                $dataValue = (int) $dataValue;
554
                                            }
555 3
                                            $formatting = NumberFormat::FORMAT_CURRENCY_USD_SIMPLE;
556
557 3
                                            break;
558 7
                                        case 'float':
559 7
                                            $type = DataType::TYPE_NUMERIC;
560 7
                                            $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
561
562 7
                                            if (floor($dataValue) == $dataValue) {
563 7
                                                if ($dataValue == (int) $dataValue) {
564 7
                                                    $dataValue = (int) $dataValue;
565
                                                } else {
566
                                                    $dataValue = (float) $dataValue;
567
                                                }
568
                                            }
569
570 7
                                            break;
571 5
                                        case 'date':
572 5
                                            $type = DataType::TYPE_NUMERIC;
573 5
                                            $value = $cellData->getAttributeNS($officeNs, 'date-value');
574
575 5
                                            $dateObj = new DateTime($value, $GMT);
576 5
                                            $dateObj->setTimeZone($timezoneObj);
577 5
                                            list($year, $month, $day, $hour, $minute, $second) = explode(
578 5
                                                ' ',
579 5
                                                $dateObj->format('Y m d H i s')
580
                                            );
581
582 5
                                            $dataValue = Date::formattedPHPToExcel(
583 5
                                                $year,
0 ignored issues
show
Bug introduced by
$year of type string is incompatible with the type integer expected by parameter $year of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

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

583
                                                /** @scrutinizer ignore-type */ $year,
Loading history...
584 5
                                                $month,
0 ignored issues
show
Bug introduced by
$month of type string is incompatible with the type integer expected by parameter $month of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

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

584
                                                /** @scrutinizer ignore-type */ $month,
Loading history...
585 5
                                                $day,
0 ignored issues
show
Bug introduced by
$day of type string is incompatible with the type integer expected by parameter $day of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

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

585
                                                /** @scrutinizer ignore-type */ $day,
Loading history...
586 5
                                                $hour,
0 ignored issues
show
Bug introduced by
$hour of type string is incompatible with the type integer expected by parameter $hours of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

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

586
                                                /** @scrutinizer ignore-type */ $hour,
Loading history...
587 5
                                                $minute,
0 ignored issues
show
Bug introduced by
$minute of type string is incompatible with the type integer expected by parameter $minutes of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

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

587
                                                /** @scrutinizer ignore-type */ $minute,
Loading history...
588 5
                                                $second
0 ignored issues
show
Bug introduced by
$second of type string is incompatible with the type integer expected by parameter $seconds of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

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

588
                                                /** @scrutinizer ignore-type */ $second
Loading history...
589
                                            );
590
591 5
                                            if ($dataValue != floor($dataValue)) {
592 1
                                                $formatting = NumberFormat::FORMAT_DATE_XLSX15
593 1
                                                    . ' '
594 5
                                                    . NumberFormat::FORMAT_DATE_TIME4;
595
                                            } else {
596 5
                                                $formatting = NumberFormat::FORMAT_DATE_XLSX15;
597
                                            }
598
599 5
                                            break;
600 5
                                        case 'time':
601 5
                                            $type = DataType::TYPE_NUMERIC;
602
603 5
                                            $timeValue = $cellData->getAttributeNS($officeNs, 'time-value');
604
605 5
                                            $dataValue = Date::PHPToExcel(
606 5
                                                strtotime(
607 5
                                                    '01-01-1970 ' . implode(':', sscanf($timeValue, 'PT%dH%dM%dS'))
608
                                                )
609
                                            );
610 5
                                            $formatting = NumberFormat::FORMAT_DATE_TIME4;
611
612 5
                                            break;
613
                                        default:
614 12
                                            $dataValue = null;
615
                                    }
616
                                } else {
617 11
                                    $type = DataType::TYPE_NULL;
618 11
                                    $dataValue = null;
619
                                }
620
621 12
                                if ($hasCalculatedValue) {
622 5
                                    $type = DataType::TYPE_FORMULA;
623 5
                                    $cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1);
624 5
                                    $temp = explode('"', $cellDataFormula);
625 5
                                    $tKey = false;
626 5
                                    foreach ($temp as &$value) {
627
                                        // Only replace in alternate array entries (i.e. non-quoted blocks)
628 5
                                        if ($tKey = !$tKey) {
0 ignored issues
show
introduced by
The condition $tKey is always false.
Loading history...
629
                                            // Cell range reference in another sheet
630 5
                                            $value = preg_replace('/\[([^\.]+)\.([^\.]+):\.([^\.]+)\]/U', '$1!$2:$3', $value);
631
632
                                            // Cell reference in another sheet
633 5
                                            $value = preg_replace('/\[([^\.]+)\.([^\.]+)\]/U', '$1!$2', $value);
634
635
                                            // Cell range reference
636 5
                                            $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/U', '$1:$2', $value);
637
638
                                            // Simple cell reference
639 5
                                            $value = preg_replace('/\[\.([^\.]+)\]/U', '$1', $value);
640
641 5
                                            $value = Calculation::translateSeparator(';', ',', $value, $inBraces);
642
                                        }
643
                                    }
644 5
                                    unset($value);
645
646
                                    // Then rebuild the formula string
647 5
                                    $cellDataFormula = implode('"', $temp);
648
                                }
649
650 12
                                if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
651 11
                                    $colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
652
                                } else {
653 12
                                    $colRepeats = 1;
654
                                }
655
656 12
                                if ($type !== null) {
657 12
                                    for ($i = 0; $i < $colRepeats; ++$i) {
658 12
                                        if ($i > 0) {
659 11
                                            ++$columnID;
660
                                        }
661
662 12
                                        if ($type !== DataType::TYPE_NULL) {
663 12
                                            for ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) {
664 12
                                                $rID = $rowID + $rowAdjust;
665
666 12
                                                $cell = $spreadsheet->getActiveSheet()
667 12
                                                    ->getCell($columnID . $rID);
668
669
                                                // Set value
670 12
                                                if ($hasCalculatedValue) {
671 5
                                                    $cell->setValueExplicit($cellDataFormula, $type);
672
                                                } else {
673 12
                                                    $cell->setValueExplicit($dataValue, $type);
674
                                                }
675
676 12
                                                if ($hasCalculatedValue) {
677 5
                                                    $cell->setCalculatedValue($dataValue);
678
                                                }
679
680
                                                // Set other properties
681 12
                                                if ($formatting !== null) {
682 7
                                                    $spreadsheet->getActiveSheet()
683 7
                                                        ->getStyle($columnID . $rID)
684 7
                                                        ->getNumberFormat()
685 7
                                                        ->setFormatCode($formatting);
686
                                                } else {
687 12
                                                    $spreadsheet->getActiveSheet()
688 12
                                                        ->getStyle($columnID . $rID)
689 12
                                                        ->getNumberFormat()
690 12
                                                        ->setFormatCode(NumberFormat::FORMAT_GENERAL);
691
                                                }
692
693 12
                                                if ($hyperlink !== null) {
694 5
                                                    $cell->getHyperlink()
695 5
                                                        ->setUrl($hyperlink);
696
                                                }
697
                                            }
698
                                        }
699
                                    }
700
                                }
701
702
                                // Merged cells
703 12
                                if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')
704 12
                                    || $cellData->hasAttributeNS($tableNs, 'number-rows-spanned')
705
                                ) {
706 6
                                    if (($type !== DataType::TYPE_NULL) || (!$this->readDataOnly)) {
707 6
                                        $columnTo = $columnID;
708
709 6
                                        if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) {
710 6
                                            $columnIndex = Coordinate::columnIndexFromString($columnID);
711 6
                                            $columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned');
712 6
                                            $columnIndex -= 2;
713
714 6
                                            $columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1);
715
                                        }
716
717 6
                                        $rowTo = $rowID;
718
719 6
                                        if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) {
720 6
                                            $rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1;
721
                                        }
722
723 6
                                        $cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo;
724 6
                                        $spreadsheet->getActiveSheet()->mergeCells($cellRange);
725
                                    }
726
                                }
727
728 12
                                ++$columnID;
729
                            }
730 12
                            $rowID += $rowRepeats;
731
732 12
                            break;
733
                    }
734
                }
735 12
                ++$worksheetID;
736
            }
737
        }
738
739
        // Return
740 12
        return $spreadsheet;
741
    }
742
743
    /**
744
     * Recursively scan element.
745
     *
746
     * @param \DOMNode $element
747
     *
748
     * @return string
749
     */
750 12
    protected function scanElementForText(\DOMNode $element)
751
    {
752 12
        $str = '';
753 12
        foreach ($element->childNodes as $child) {
754
            /** @var \DOMNode $child */
755 12
            if ($child->nodeType == XML_TEXT_NODE) {
756 12
                $str .= $child->nodeValue;
757 7
            } elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:s') {
758
                // It's a space
759
760
                // Multiple spaces?
761
                /** @var \DOMAttr $cAttr */
762 3
                $cAttr = $child->attributes->getNamedItem('c');
763 3
                if ($cAttr) {
764 3
                    $multiplier = (int) $cAttr->nodeValue;
765
                } else {
766 3
                    $multiplier = 1;
767
                }
768
769 3
                $str .= str_repeat(' ', $multiplier);
770
            }
771
772 12
            if ($child->hasChildNodes()) {
773 12
                $str .= $this->scanElementForText($child);
774
            }
775
        }
776
777 12
        return $str;
778
    }
779
780
    /**
781
     * @param string $is
782
     *
783
     * @return RichText
784
     */
785 6
    private function parseRichText($is)
786
    {
787 6
        $value = new RichText();
788 6
        $value->createText($is);
789
790 6
        return $value;
791
    }
792
}
793