Completed
Push — develop ( f99eb8...c5339b )
by Adrien
31:45
created

Ods::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 8
ccs 3
cts 3
cp 1
crap 1
rs 9.4285
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;
8
use PhpOffice\PhpSpreadsheet\Cell\DataType;
9
use PhpOffice\PhpSpreadsheet\Shared\File;
10
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
11
12
/**
13
 * Copyright (c) 2006 - 2016 PhpSpreadsheet.
14
 *
15
 * This library is free software; you can redistribute it and/or
16
 * modify it under the terms of the GNU Lesser General Public
17
 * License as published by the Free Software Foundation; either
18
 * version 2.1 of the License, or (at your option) any later version.
19
 *
20
 * This library is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23
 * Lesser General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Lesser General Public
26
 * License along with this library; if not, write to the Free Software
27
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
28
 *
29
 * @category   PhpSpreadsheet
30
 *
31
 * @copyright  Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
32
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
33
 */
34
class Ods extends BaseReader implements IReader
35
{
36
    /**
37
     * Formats.
38
     *
39
     * @var array
40
     */
41
    private $styles = [];
0 ignored issues
show
Unused Code introduced by
The property $styles is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
42
43
    /**
44
     * Create a new Ods Reader instance.
45
     */
46 9
    public function __construct()
47
    {
48 9
        $this->readFilter = new DefaultReadFilter();
49 9
    }
50
51
    /**
52
     * Can the current IReader read the file?
53
     *
54
     * @param string $pFilename
55
     *
56
     * @throws Exception
57
     *
58
     * @return bool
59
     */
60 2
    public function canRead($pFilename)
61
    {
62 2
        File::assertFile($pFilename);
63
64 2
        $zipClass = \PhpOffice\PhpSpreadsheet\Settings::getZipClass();
65
66 2
        $mimeType = 'UNKNOWN';
67
68
        // Load file
69
70
        /** @var \ZipArchive $zip */
71 2
        $zip = new $zipClass();
72 2
        if ($zip->open($pFilename) === true) {
73
            // check if it is an OOXML archive
74 2
            $stat = $zip->statName('mimetype');
75 2
            if ($stat && ($stat['size'] <= 255)) {
76 2
                $mimeType = $zip->getFromName($stat['name']);
77
            } elseif ($stat = $zip->statName('META-INF/manifest.xml')) {
0 ignored issues
show
Unused Code introduced by
$stat is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
78
                $xml = simplexml_load_string(
79
                    $this->securityScan($zip->getFromName('META-INF/manifest.xml')),
80
                    'SimpleXMLElement',
81
                    \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
82
                );
83
                $namespacesContent = $xml->getNamespaces(true);
84
                if (isset($namespacesContent['manifest'])) {
85
                    $manifest = $xml->children($namespacesContent['manifest']);
86
                    foreach ($manifest as $manifestDataSet) {
87
                        $manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']);
88
                        if ($manifestAttributes->{'full-path'} == '/') {
89
                            $mimeType = (string) $manifestAttributes->{'media-type'};
90
                            break;
91
                        }
92
                    }
93
                }
94
            }
95
96 2
            $zip->close();
97
98 2
            return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet';
99
        }
100
101
        return false;
102
    }
103
104
    /**
105
     * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
106
     *
107
     * @param string $pFilename
108
     *
109
     * @throws Exception
110
     *
111
     * @return string[]
112
     */
113 1
    public function listWorksheetNames($pFilename)
114
    {
115 1
        File::assertFile($pFilename);
116
117 1
        $zipClass = \PhpOffice\PhpSpreadsheet\Settings::getZipClass();
118
119
        /** @var \ZipArchive $zip */
120 1
        $zip = new $zipClass();
121 1
        if (!$zip->open($pFilename)) {
122
            throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.');
123
        }
124
125 1
        $worksheetNames = [];
126
127 1
        $xml = new \XMLReader();
128 1
        $xml->xml(
129 1
            $this->securityScanFile('zip://' . realpath($pFilename) . '#content.xml'),
130 1
            null,
131 1
            \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
132
        );
133 1
        $xml->setParserProperty(2, true);
134
135
        // Step into the first level of content of the XML
136 1
        $xml->read();
137 1
        while ($xml->read()) {
138
            // Quickly jump through to the office:body node
139 1
            while ($xml->name !== 'office:body') {
140 1
                if ($xml->isEmptyElement) {
141 1
                    $xml->read();
142
                } else {
143 1
                    $xml->next();
144
                }
145
            }
146
            // Now read each node until we find our first table:table node
147 1
            while ($xml->read()) {
148 1
                if ($xml->name == 'table:table' && $xml->nodeType == \XMLReader::ELEMENT) {
149
                    // Loop through each table:table node reading the table:name attribute for each worksheet name
150
                    do {
151 1
                        $worksheetNames[] = $xml->getAttribute('table:name');
152 1
                        $xml->next();
153 1
                    } while ($xml->name == 'table:table' && $xml->nodeType == \XMLReader::ELEMENT);
154
                }
155
            }
156
        }
157
158 1
        return $worksheetNames;
159
    }
160
161
    /**
162
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
163
     *
164
     * @param string $pFilename
165
     *
166
     * @throws Exception
167
     *
168
     * @return array
169
     */
170
    public function listWorksheetInfo($pFilename)
171
    {
172
        File::assertFile($pFilename);
173
174
        $worksheetInfo = [];
175
176
        $zipClass = \PhpOffice\PhpSpreadsheet\Settings::getZipClass();
177
178
        /** @var \ZipArchive $zip */
179
        $zip = new $zipClass();
180
        if (!$zip->open($pFilename)) {
181
            throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.');
182
        }
183
184
        $xml = new \XMLReader();
185
        $res = $xml->xml(
0 ignored issues
show
Unused Code introduced by
$res is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
186
            $this->securityScanFile('zip://' . realpath($pFilename) . '#content.xml'),
187
            null,
188
            \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
189
        );
190
        $xml->setParserProperty(2, true);
191
192
        // Step into the first level of content of the XML
193
        $xml->read();
194
        while ($xml->read()) {
195
            // Quickly jump through to the office:body node
196
            while ($xml->name !== 'office:body') {
197
                if ($xml->isEmptyElement) {
198
                    $xml->read();
199
                } else {
200
                    $xml->next();
201
                }
202
            }
203
                // Now read each node until we find our first table:table node
204
            while ($xml->read()) {
205
                if ($xml->name == 'table:table' && $xml->nodeType == \XMLReader::ELEMENT) {
206
                    $worksheetNames[] = $xml->getAttribute('table:name');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$worksheetNames was never initialized. Although not strictly required by PHP, it is generally a good practice to add $worksheetNames = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
207
208
                    $tmpInfo = [
209
                        'worksheetName' => $xml->getAttribute('table:name'),
210
                        'lastColumnLetter' => 'A',
211
                        'lastColumnIndex' => 0,
212
                        'totalRows' => 0,
213
                        'totalColumns' => 0,
214
                    ];
215
216
                    // Loop through each child node of the table:table element reading
217
                    $currCells = 0;
218
                    do {
219
                        $xml->read();
220
                        if ($xml->name == 'table:table-row' && $xml->nodeType == \XMLReader::ELEMENT) {
221
                            $rowspan = $xml->getAttribute('table:number-rows-repeated');
222
                            $rowspan = empty($rowspan) ? 1 : $rowspan;
223
                            $tmpInfo['totalRows'] += $rowspan;
224
                            $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
225
                            $currCells = 0;
226
                            // Step into the row
227
                            $xml->read();
228
                            do {
229
                                if ($xml->name == 'table:table-cell' && $xml->nodeType == \XMLReader::ELEMENT) {
230
                                    if (!$xml->isEmptyElement) {
231
                                        ++$currCells;
232
                                        $xml->next();
233
                                    } else {
234
                                        $xml->read();
235
                                    }
236
                                } elseif ($xml->name == 'table:covered-table-cell' && $xml->nodeType == \XMLReader::ELEMENT) {
237
                                    $mergeSize = $xml->getAttribute('table:number-columns-repeated');
238
                                    $currCells += $mergeSize;
239
                                    $xml->read();
240
                                }
241
                            } while ($xml->name != 'table:table-row');
242
                        }
243
                    } while ($xml->name != 'table:table');
244
245
                    $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
246
                    $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
247
                    $tmpInfo['lastColumnLetter'] = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($tmpInfo['lastColumnIndex']);
248
                    $worksheetInfo[] = $tmpInfo;
249
                }
250
            }
251
        }
252
253
        return $worksheetInfo;
254
    }
255
256
    /**
257
     * Loads PhpSpreadsheet from file.
258
     *
259
     * @param string $pFilename
260
     *
261
     * @throws Exception
262
     *
263
     * @return \PhpOffice\PhpSpreadsheet\Spreadsheet
264
     */
265 1
    public function load($pFilename)
266
    {
267
        // Create new Spreadsheet
268 1
        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
269
270
        // Load into this instance
271 1
        return $this->loadIntoExisting($pFilename, $spreadsheet);
272
    }
273
274 View Code Duplication
    private static function identifyFixedStyleValue($styleList, &$styleAttributeValue)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
275
    {
276
        $styleAttributeValue = strtolower($styleAttributeValue);
277
        foreach ($styleList as $style) {
278
            if ($styleAttributeValue == strtolower($style)) {
279
                $styleAttributeValue = $style;
280
281
                return true;
282
            }
283
        }
284
285
        return false;
286
    }
287
288
    /**
289
     * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
290
     *
291
     * @param string $pFilename
292
     * @param \PhpOffice\PhpSpreadsheet\Spreadsheet $spreadsheet
293
     *
294
     * @throws Exception
295
     *
296
     * @return \PhpOffice\PhpSpreadsheet\Spreadsheet
297
     */
298 7
    public function loadIntoExisting($pFilename, \PhpOffice\PhpSpreadsheet\Spreadsheet $spreadsheet)
299
    {
300 7
        File::assertFile($pFilename);
301
302 7
        $timezoneObj = new DateTimeZone('Europe/London');
303 7
        $GMT = new \DateTimeZone('UTC');
304
305 7
        $zipClass = \PhpOffice\PhpSpreadsheet\Settings::getZipClass();
306
307
        /** @var \ZipArchive $zip */
308 7
        $zip = new $zipClass();
309 7
        if (!$zip->open($pFilename)) {
310
            throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.');
311
        }
312
313
        /*
314
         * Meta
315
         */
316
317 7
        $xml = simplexml_load_string(
318 7
            $this->securityScan($zip->getFromName('meta.xml')),
319 7
            'SimpleXMLElement',
320 7
            \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
321
        );
322 7
        $namespacesMeta = $xml->getNamespaces(true);
323
324 7
        $docProps = $spreadsheet->getProperties();
325 7
        $officeProperty = $xml->children($namespacesMeta['office']);
326 7
        foreach ($officeProperty as $officePropertyData) {
327 7
            $officePropertyDC = [];
328 7
            if (isset($namespacesMeta['dc'])) {
329 7
                $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']);
330
            }
331 7
            foreach ($officePropertyDC as $propertyName => $propertyValue) {
332 7
                $propertyValue = (string) $propertyValue;
333
                switch ($propertyName) {
334 7
                    case 'title':
335 5
                        $docProps->setTitle($propertyValue);
336 5
                        break;
337 7
                    case 'subject':
338 5
                        $docProps->setSubject($propertyValue);
339 5
                        break;
340 7
                    case 'creator':
341
                        $docProps->setCreator($propertyValue);
342
                        $docProps->setLastModifiedBy($propertyValue);
343
                        break;
344 7
                    case 'date':
345 7
                        $creationDate = strtotime($propertyValue);
346 7
                        $docProps->setCreated($creationDate);
0 ignored issues
show
Documentation introduced by
$creationDate is of type integer, but the function expects a object<PhpOffice\PhpSpre...Document\datetime>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
347 7
                        $docProps->setModified($creationDate);
0 ignored issues
show
Documentation introduced by
$creationDate is of type integer, but the function expects a object<PhpOffice\PhpSpre...Document\datetime>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
348 7
                        break;
349 5
                    case 'description':
350 5
                        $docProps->setDescription($propertyValue);
351 5
                        break;
352
                }
353
            }
354 7
            $officePropertyMeta = [];
355 7
            if (isset($namespacesMeta['dc'])) {
356 7
                $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
357
            }
358 7
            foreach ($officePropertyMeta as $propertyName => $propertyValue) {
359 7
                $propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']);
360 7
                $propertyValue = (string) $propertyValue;
361
                switch ($propertyName) {
362 7
                    case 'initial-creator':
363 5
                        $docProps->setCreator($propertyValue);
364 5
                        break;
365 7
                    case 'keyword':
366 5
                        $docProps->setKeywords($propertyValue);
367 5
                        break;
368 7
                    case 'creation-date':
369 7
                        $creationDate = strtotime($propertyValue);
370 7
                        $docProps->setCreated($creationDate);
0 ignored issues
show
Documentation introduced by
$creationDate is of type integer, but the function expects a object<PhpOffice\PhpSpre...Document\datetime>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
371 7
                        break;
372 7
                    case 'user-defined':
373 5
                        $propertyValueType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_STRING;
374 5
                        foreach ($propertyValueAttributes as $key => $value) {
375 5
                            if ($key == 'name') {
376 5
                                $propertyValueName = (string) $value;
377
                            } elseif ($key == 'value-type') {
378
                                switch ($value) {
379
                                    case 'date':
380
                                        $propertyValue = \PhpOffice\PhpSpreadsheet\Document\Properties::convertProperty($propertyValue, 'date');
381
                                        $propertyValueType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_DATE;
382
                                        break;
383
                                    case 'boolean':
384
                                        $propertyValue = \PhpOffice\PhpSpreadsheet\Document\Properties::convertProperty($propertyValue, 'bool');
385
                                        $propertyValueType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_BOOLEAN;
386
                                        break;
387
                                    case 'float':
388
                                        $propertyValue = \PhpOffice\PhpSpreadsheet\Document\Properties::convertProperty($propertyValue, 'r4');
389
                                        $propertyValueType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_FLOAT;
390
                                        break;
391
                                    default:
392
                                        $propertyValueType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_STRING;
393
                                }
394
                            }
395
                        }
396 5
                        $docProps->setCustomProperty($propertyValueName, $propertyValue, $propertyValueType);
0 ignored issues
show
Bug introduced by
The variable $propertyValueName does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
397 7
                        break;
398
                }
399
            }
400
        }
401
402
        /*
403
         * Content
404
         */
405
406 7
        $dom = new \DOMDocument('1.01', 'UTF-8');
407 7
        $dom->loadXML(
408 7
            $this->securityScan($zip->getFromName('content.xml')),
409 7
            \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
410
        );
411
412 7
        $officeNs = $dom->lookupNamespaceUri('office');
413 7
        $tableNs = $dom->lookupNamespaceUri('table');
414 7
        $textNs = $dom->lookupNamespaceUri('text');
415 7
        $xlinkNs = $dom->lookupNamespaceUri('xlink');
416
417 7
        $spreadsheets = $dom->getElementsByTagNameNS($officeNs, 'body')
418 7
            ->item(0)
419 7
            ->getElementsByTagNameNS($officeNs, 'spreadsheet');
420
421 7
        foreach ($spreadsheets as $workbookData) {
422
            /** @var \DOMElement $workbookData */
423 7
            $tables = $workbookData->getElementsByTagNameNS($tableNs, 'table');
424
425 7
            $worksheetID = 0;
426 7
            foreach ($tables as $worksheetDataSet) {
427
                /** @var \DOMElement $worksheetDataSet */
428 7
                $worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name');
429
430
                // Check loadSheetsOnly
431 7
                if (isset($this->loadSheetsOnly)
432
                    && $worksheetName
433
                    && !in_array($worksheetName, $this->loadSheetsOnly)) {
434
                    continue;
435
                }
436
437
                // Create sheet
438 7
                if ($worksheetID > 0) {
439 7
                    $spreadsheet->createSheet(); // First sheet is added by default
440
                }
441 7
                $spreadsheet->setActiveSheetIndex($worksheetID);
442
443 7
                if ($worksheetName) {
444
                    // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
445
                    // formula cells... during the load, all formulae should be correct, and we're simply
446
                    // bringing the worksheet name in line with the formula, not the reverse
447 7
                    $spreadsheet->getActiveSheet()->setTitle($worksheetName, false);
448
                }
449
450
                // Go through every child of table element
451 7
                $rowID = 1;
452 7
                foreach ($worksheetDataSet->childNodes as $childNode) {
453
                    /** @var \DOMElement $childNode */
454
455
                    // Filter elements which are not under the "table" ns
456 7
                    if ($childNode->namespaceURI != $tableNs) {
457 5
                        continue;
458
                    }
459
460 7
                    $key = $childNode->nodeName;
461
462
                    // Remove ns from node name
463 7
                    if (strpos($key, ':') !== false) {
464 7
                        $keyChunks = explode(':', $key);
465 7
                        $key = array_pop($keyChunks);
466
                    }
467
468
                    switch ($key) {
469 7
                        case 'table-header-rows':
470
                            /// TODO :: Figure this out. This is only a partial implementation I guess.
471
                            //          ($rowData it's not used at all and I'm not sure that PHPExcel
472
                            //          has an API for this)
473
474
//                            foreach ($rowData as $keyRowData => $cellData) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
49% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
475
//                                $rowData = $cellData;
476
//                                break;
477
//                            }
478
                            break;
479 7
                        case 'table-row':
480 7
                            if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) {
481 7
                                $rowRepeats = $childNode->getAttributeNS($tableNs, 'number-rows-repeated');
482
                            } else {
483 7
                                $rowRepeats = 1;
484
                            }
485
486 7
                            $columnID = 'A';
487 7
                            foreach ($childNode->childNodes as $key => $cellData) {
488
                                /* @var \DOMElement $cellData */
489
490 7 View Code Duplication
                                if ($this->getReadFilter() !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
491 7
                                    if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
492
                                        ++$columnID;
493
                                        continue;
494
                                    }
495
                                }
496
497
                                // Initialize variables
498 7
                                $formatting = $hyperlink = null;
499 7
                                $hasCalculatedValue = false;
500 7
                                $cellDataFormula = '';
501
502 7
                                if ($cellData->hasAttributeNS($tableNs, 'formula')) {
503 5
                                    $cellDataFormula = $cellData->getAttributeNS($tableNs, 'formula');
504 5
                                    $hasCalculatedValue = true;
505
                                }
506
507
                                // Annotations
508 7
                                $annotation = $cellData->getElementsByTagNameNS($officeNs, 'annotation');
509
510 7
                                if ($annotation->length > 0) {
511 5
                                    $textNode = $annotation->item(0)->getElementsByTagNameNS($textNs, 'p');
512
513 5
                                    if ($textNode->length > 0) {
514 5
                                        $text = $this->scanElementForText($textNode->item(0));
515
516 5
                                        $spreadsheet->getActiveSheet()
517 5
                                            ->getComment($columnID . $rowID)
518 5
                                            ->setText($this->parseRichText($text));
519
//                                                                    ->setAuthor( $author )
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
520
                                    }
521
                                }
522
523
                                // Content
524
525
                                /** @var \DOMElement[] $paragraphs */
526 7
                                $paragraphs = [];
527
528 7
                                foreach ($cellData->childNodes as $item) {
529
                                    /** @var \DOMElement $item */
530
531
                                    // Filter text:p elements
532 7
                                    if ($item->nodeName == 'text:p') {
533 7
                                        $paragraphs[] = $item;
534
                                    }
535
                                }
536
537 7
                                if (count($paragraphs) > 0) {
538
                                    // Consolidate if there are multiple p records (maybe with spans as well)
539 7
                                    $dataArray = [];
540
541
                                    // Text can have multiple text:p and within those, multiple text:span.
542
                                    // text:p newlines, but text:span does not.
543
                                    // Also, here we assume there is no text data is span fields are specified, since
544
                                    // we have no way of knowing proper positioning anyway.
545
546 7
                                    foreach ($paragraphs as $pData) {
547 7
                                        $dataArray[] = $this->scanElementForText($pData);
548
                                    }
549 7
                                    $allCellDataText = implode($dataArray, "\n");
550
551 7
                                    $type = $cellData->getAttributeNS($officeNs, 'value-type');
552
553
                                    switch ($type) {
554 7
                                        case 'string':
555 7
                                            $type = DataType::TYPE_STRING;
556 7
                                            $dataValue = $allCellDataText;
557
558 7
                                            foreach ($paragraphs as $paragraph) {
559 7
                                                $link = $paragraph->getElementsByTagNameNS($textNs, 'a');
560 7
                                                if ($link->length > 0) {
561 5
                                                    $hyperlink = $link->item(0)->getAttributeNS($xlinkNs, 'href');
562
                                                }
563
                                            }
564
565 7
                                            break;
566 7
                                        case 'boolean':
567 5
                                            $type = DataType::TYPE_BOOL;
568 5
                                            $dataValue = ($allCellDataText == 'TRUE') ? true : false;
569 5
                                            break;
570 7 View Code Duplication
                                        case 'percentage':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
571 3
                                            $type = DataType::TYPE_NUMERIC;
572 3
                                            $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
573
574 3
                                            if (floor($dataValue) == $dataValue) {
575
                                                $dataValue = (int) $dataValue;
576
                                            }
577 3
                                            $formatting = \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_PERCENTAGE_00;
578 3
                                            break;
579 7 View Code Duplication
                                        case 'currency':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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