Passed
Push — develop ( 3bea6f...0f8f07 )
by Mark
36:13
created

Ods   F

Complexity

Total Complexity 124

Size/Duplication

Total Lines 773
Duplicated Lines 0 %

Test Coverage

Coverage 78.8%

Importance

Changes 0
Metric Value
eloc 411
dl 0
loc 773
ccs 316
cts 401
cp 0.788
rs 2
c 0
b 0
f 0
wmc 124

8 Methods

Rating   Name   Duplication   Size   Complexity  
D listWorksheetInfo() 0 81 18
B listWorksheetNames() 0 43 9
A __construct() 0 4 1
B scanElementForText() 0 28 7
B canRead() 0 40 8
A load() 0 7 1
F loadIntoExisting() 0 482 79
A parseRichText() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Ods often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Ods, and based on these observations, apply Extract Interface, too.

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

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

586
                                                /** @scrutinizer ignore-type */ $year,
Loading history...
587 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

587
                                                /** @scrutinizer ignore-type */ $month,
Loading history...
588 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

588
                                                /** @scrutinizer ignore-type */ $day,
Loading history...
589 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

589
                                                /** @scrutinizer ignore-type */ $hour,
Loading history...
590 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

590
                                                /** @scrutinizer ignore-type */ $minute,
Loading history...
591 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

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