Completed
Push — develop ( c5339b...ea5663 )
by Adrien
19:12
created

Ods::parseRichText()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 7
ccs 4
cts 4
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
use ZipArchive;
12
13
/**
14
 * Copyright (c) 2006 - 2016 PhpSpreadsheet.
15
 *
16
 * This library is free software; you can redistribute it and/or
17
 * modify it under the terms of the GNU Lesser General Public
18
 * License as published by the Free Software Foundation; either
19
 * version 2.1 of the License, or (at your option) any later version.
20
 *
21
 * This library is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24
 * Lesser General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Lesser General Public
27
 * License along with this library; if not, write to the Free Software
28
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
29
 *
30
 * @category   PhpSpreadsheet
31
 *
32
 * @copyright  Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
33
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
34
 */
35
class Ods extends BaseReader implements IReader
36
{
37
    /**
38
     * Formats.
39
     *
40
     * @var array
41
     */
42
    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...
43
44
    /**
45
     * Create a new Ods Reader instance.
46
     */
47 9
    public function __construct()
48
    {
49 9
        $this->readFilter = new DefaultReadFilter();
50 9
    }
51
52
    /**
53
     * Can the current IReader read the file?
54
     *
55
     * @param string $pFilename
56
     *
57
     * @throws Exception
58
     *
59
     * @return bool
60
     */
61 2
    public function canRead($pFilename)
62
    {
63 2
        File::assertFile($pFilename);
64
65 2
        $mimeType = 'UNKNOWN';
66
67
        // Load file
68
69 2
        $zip = new ZipArchive();
70 2
        if ($zip->open($pFilename) === true) {
71
            // check if it is an OOXML archive
72 2
            $stat = $zip->statName('mimetype');
73 2
            if ($stat && ($stat['size'] <= 255)) {
74 2
                $mimeType = $zip->getFromName($stat['name']);
75
            } 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...
76
                $xml = simplexml_load_string(
77
                    $this->securityScan($zip->getFromName('META-INF/manifest.xml')),
78
                    'SimpleXMLElement',
79
                    \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
80
                );
81
                $namespacesContent = $xml->getNamespaces(true);
82
                if (isset($namespacesContent['manifest'])) {
83
                    $manifest = $xml->children($namespacesContent['manifest']);
84
                    foreach ($manifest as $manifestDataSet) {
85
                        $manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']);
86
                        if ($manifestAttributes->{'full-path'} == '/') {
87
                            $mimeType = (string) $manifestAttributes->{'media-type'};
88
                            break;
89
                        }
90
                    }
91
                }
92
            }
93
94 2
            $zip->close();
95
96 2
            return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet';
97
        }
98
99
        return false;
100
    }
101
102
    /**
103
     * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
104
     *
105
     * @param string $pFilename
106
     *
107
     * @throws Exception
108
     *
109
     * @return string[]
110
     */
111 1
    public function listWorksheetNames($pFilename)
112
    {
113 1
        File::assertFile($pFilename);
114
115 1
        $zip = new ZipArchive();
116 1
        if (!$zip->open($pFilename)) {
117
            throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.');
118
        }
119
120 1
        $worksheetNames = [];
121
122 1
        $xml = new \XMLReader();
123 1
        $xml->xml(
124 1
            $this->securityScanFile('zip://' . realpath($pFilename) . '#content.xml'),
125 1
            null,
126 1
            \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
127
        );
128 1
        $xml->setParserProperty(2, true);
129
130
        // Step into the first level of content of the XML
131 1
        $xml->read();
132 1
        while ($xml->read()) {
133
            // Quickly jump through to the office:body node
134 1
            while ($xml->name !== 'office:body') {
135 1
                if ($xml->isEmptyElement) {
136 1
                    $xml->read();
137
                } else {
138 1
                    $xml->next();
139
                }
140
            }
141
            // Now read each node until we find our first table:table node
142 1
            while ($xml->read()) {
143 1
                if ($xml->name == 'table:table' && $xml->nodeType == \XMLReader::ELEMENT) {
144
                    // Loop through each table:table node reading the table:name attribute for each worksheet name
145
                    do {
146 1
                        $worksheetNames[] = $xml->getAttribute('table:name');
147 1
                        $xml->next();
148 1
                    } while ($xml->name == 'table:table' && $xml->nodeType == \XMLReader::ELEMENT);
149
                }
150
            }
151
        }
152
153 1
        return $worksheetNames;
154
    }
155
156
    /**
157
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
158
     *
159
     * @param string $pFilename
160
     *
161
     * @throws Exception
162
     *
163
     * @return array
164
     */
165
    public function listWorksheetInfo($pFilename)
166
    {
167
        File::assertFile($pFilename);
168
169
        $worksheetInfo = [];
170
171
        $zip = new ZipArchive();
172
        if (!$zip->open($pFilename)) {
173
            throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.');
174
        }
175
176
        $xml = new \XMLReader();
177
        $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...
178
            $this->securityScanFile('zip://' . realpath($pFilename) . '#content.xml'),
179
            null,
180
            \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
181
        );
182
        $xml->setParserProperty(2, true);
183
184
        // Step into the first level of content of the XML
185
        $xml->read();
186
        while ($xml->read()) {
187
            // Quickly jump through to the office:body node
188
            while ($xml->name !== 'office:body') {
189
                if ($xml->isEmptyElement) {
190
                    $xml->read();
191
                } else {
192
                    $xml->next();
193
                }
194
            }
195
                // Now read each node until we find our first table:table node
196
            while ($xml->read()) {
197
                if ($xml->name == 'table:table' && $xml->nodeType == \XMLReader::ELEMENT) {
198
                    $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...
199
200
                    $tmpInfo = [
201
                        'worksheetName' => $xml->getAttribute('table:name'),
202
                        'lastColumnLetter' => 'A',
203
                        'lastColumnIndex' => 0,
204
                        'totalRows' => 0,
205
                        'totalColumns' => 0,
206
                    ];
207
208
                    // Loop through each child node of the table:table element reading
209
                    $currCells = 0;
210
                    do {
211
                        $xml->read();
212
                        if ($xml->name == 'table:table-row' && $xml->nodeType == \XMLReader::ELEMENT) {
213
                            $rowspan = $xml->getAttribute('table:number-rows-repeated');
214
                            $rowspan = empty($rowspan) ? 1 : $rowspan;
215
                            $tmpInfo['totalRows'] += $rowspan;
216
                            $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
217
                            $currCells = 0;
218
                            // Step into the row
219
                            $xml->read();
220
                            do {
221
                                if ($xml->name == 'table:table-cell' && $xml->nodeType == \XMLReader::ELEMENT) {
222
                                    if (!$xml->isEmptyElement) {
223
                                        ++$currCells;
224
                                        $xml->next();
225
                                    } else {
226
                                        $xml->read();
227
                                    }
228
                                } elseif ($xml->name == 'table:covered-table-cell' && $xml->nodeType == \XMLReader::ELEMENT) {
229
                                    $mergeSize = $xml->getAttribute('table:number-columns-repeated');
230
                                    $currCells += $mergeSize;
231
                                    $xml->read();
232
                                }
233
                            } while ($xml->name != 'table:table-row');
234
                        }
235
                    } while ($xml->name != 'table:table');
236
237
                    $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
238
                    $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
239
                    $tmpInfo['lastColumnLetter'] = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($tmpInfo['lastColumnIndex']);
240
                    $worksheetInfo[] = $tmpInfo;
241
                }
242
            }
243
        }
244
245
        return $worksheetInfo;
246
    }
247
248
    /**
249
     * Loads PhpSpreadsheet from file.
250
     *
251
     * @param string $pFilename
252
     *
253
     * @throws Exception
254
     *
255
     * @return \PhpOffice\PhpSpreadsheet\Spreadsheet
256
     */
257 1
    public function load($pFilename)
258
    {
259
        // Create new Spreadsheet
260 1
        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
261
262
        // Load into this instance
263 1
        return $this->loadIntoExisting($pFilename, $spreadsheet);
264
    }
265
266 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...
267
    {
268
        $styleAttributeValue = strtolower($styleAttributeValue);
269
        foreach ($styleList as $style) {
270
            if ($styleAttributeValue == strtolower($style)) {
271
                $styleAttributeValue = $style;
272
273
                return true;
274
            }
275
        }
276
277
        return false;
278
    }
279
280
    /**
281
     * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
282
     *
283
     * @param string $pFilename
284
     * @param \PhpOffice\PhpSpreadsheet\Spreadsheet $spreadsheet
285
     *
286
     * @throws Exception
287
     *
288
     * @return \PhpOffice\PhpSpreadsheet\Spreadsheet
289
     */
290 7
    public function loadIntoExisting($pFilename, \PhpOffice\PhpSpreadsheet\Spreadsheet $spreadsheet)
291
    {
292 7
        File::assertFile($pFilename);
293
294 7
        $timezoneObj = new DateTimeZone('Europe/London');
295 7
        $GMT = new \DateTimeZone('UTC');
296
297 7
        $zip = new ZipArchive();
298 7
        if (!$zip->open($pFilename)) {
299
            throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.');
300
        }
301
302
        /*
303
         * Meta
304
         */
305
306 7
        $xml = simplexml_load_string(
307 7
            $this->securityScan($zip->getFromName('meta.xml')),
308 7
            'SimpleXMLElement',
309 7
            \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
310
        );
311 7
        $namespacesMeta = $xml->getNamespaces(true);
312
313 7
        $docProps = $spreadsheet->getProperties();
314 7
        $officeProperty = $xml->children($namespacesMeta['office']);
315 7
        foreach ($officeProperty as $officePropertyData) {
316 7
            $officePropertyDC = [];
317 7
            if (isset($namespacesMeta['dc'])) {
318 7
                $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']);
319
            }
320 7
            foreach ($officePropertyDC as $propertyName => $propertyValue) {
321 7
                $propertyValue = (string) $propertyValue;
322
                switch ($propertyName) {
323 7
                    case 'title':
324 5
                        $docProps->setTitle($propertyValue);
325 5
                        break;
326 7
                    case 'subject':
327 5
                        $docProps->setSubject($propertyValue);
328 5
                        break;
329 7
                    case 'creator':
330
                        $docProps->setCreator($propertyValue);
331
                        $docProps->setLastModifiedBy($propertyValue);
332
                        break;
333 7
                    case 'date':
334 7
                        $creationDate = strtotime($propertyValue);
335 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...
336 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...
337 7
                        break;
338 5
                    case 'description':
339 5
                        $docProps->setDescription($propertyValue);
340 5
                        break;
341
                }
342
            }
343 7
            $officePropertyMeta = [];
344 7
            if (isset($namespacesMeta['dc'])) {
345 7
                $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
346
            }
347 7
            foreach ($officePropertyMeta as $propertyName => $propertyValue) {
348 7
                $propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']);
349 7
                $propertyValue = (string) $propertyValue;
350
                switch ($propertyName) {
351 7
                    case 'initial-creator':
352 5
                        $docProps->setCreator($propertyValue);
353 5
                        break;
354 7
                    case 'keyword':
355 5
                        $docProps->setKeywords($propertyValue);
356 5
                        break;
357 7
                    case 'creation-date':
358 7
                        $creationDate = strtotime($propertyValue);
359 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...
360 7
                        break;
361 7
                    case 'user-defined':
362 5
                        $propertyValueType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_STRING;
363 5
                        foreach ($propertyValueAttributes as $key => $value) {
364 5
                            if ($key == 'name') {
365 5
                                $propertyValueName = (string) $value;
366
                            } elseif ($key == 'value-type') {
367
                                switch ($value) {
368
                                    case 'date':
369
                                        $propertyValue = \PhpOffice\PhpSpreadsheet\Document\Properties::convertProperty($propertyValue, 'date');
370
                                        $propertyValueType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_DATE;
371
                                        break;
372
                                    case 'boolean':
373
                                        $propertyValue = \PhpOffice\PhpSpreadsheet\Document\Properties::convertProperty($propertyValue, 'bool');
374
                                        $propertyValueType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_BOOLEAN;
375
                                        break;
376
                                    case 'float':
377
                                        $propertyValue = \PhpOffice\PhpSpreadsheet\Document\Properties::convertProperty($propertyValue, 'r4');
378
                                        $propertyValueType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_FLOAT;
379
                                        break;
380
                                    default:
381
                                        $propertyValueType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_STRING;
382
                                }
383
                            }
384
                        }
385 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...
386 7
                        break;
387
                }
388
            }
389
        }
390
391
        /*
392
         * Content
393
         */
394
395 7
        $dom = new \DOMDocument('1.01', 'UTF-8');
396 7
        $dom->loadXML(
397 7
            $this->securityScan($zip->getFromName('content.xml')),
398 7
            \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
399
        );
400
401 7
        $officeNs = $dom->lookupNamespaceUri('office');
402 7
        $tableNs = $dom->lookupNamespaceUri('table');
403 7
        $textNs = $dom->lookupNamespaceUri('text');
404 7
        $xlinkNs = $dom->lookupNamespaceUri('xlink');
405
406 7
        $spreadsheets = $dom->getElementsByTagNameNS($officeNs, 'body')
407 7
            ->item(0)
408 7
            ->getElementsByTagNameNS($officeNs, 'spreadsheet');
409
410 7
        foreach ($spreadsheets as $workbookData) {
411
            /** @var \DOMElement $workbookData */
412 7
            $tables = $workbookData->getElementsByTagNameNS($tableNs, 'table');
413
414 7
            $worksheetID = 0;
415 7
            foreach ($tables as $worksheetDataSet) {
416
                /** @var \DOMElement $worksheetDataSet */
417 7
                $worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name');
418
419
                // Check loadSheetsOnly
420 7
                if (isset($this->loadSheetsOnly)
421
                    && $worksheetName
422
                    && !in_array($worksheetName, $this->loadSheetsOnly)) {
423
                    continue;
424
                }
425
426
                // Create sheet
427 7
                if ($worksheetID > 0) {
428 7
                    $spreadsheet->createSheet(); // First sheet is added by default
429
                }
430 7
                $spreadsheet->setActiveSheetIndex($worksheetID);
431
432 7
                if ($worksheetName) {
433
                    // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
434
                    // formula cells... during the load, all formulae should be correct, and we're simply
435
                    // bringing the worksheet name in line with the formula, not the reverse
436 7
                    $spreadsheet->getActiveSheet()->setTitle($worksheetName, false);
437
                }
438
439
                // Go through every child of table element
440 7
                $rowID = 1;
441 7
                foreach ($worksheetDataSet->childNodes as $childNode) {
442
                    /** @var \DOMElement $childNode */
443
444
                    // Filter elements which are not under the "table" ns
445 7
                    if ($childNode->namespaceURI != $tableNs) {
446 5
                        continue;
447
                    }
448
449 7
                    $key = $childNode->nodeName;
450
451
                    // Remove ns from node name
452 7
                    if (strpos($key, ':') !== false) {
453 7
                        $keyChunks = explode(':', $key);
454 7
                        $key = array_pop($keyChunks);
455
                    }
456
457
                    switch ($key) {
458 7
                        case 'table-header-rows':
459
                            /// TODO :: Figure this out. This is only a partial implementation I guess.
460
                            //          ($rowData it's not used at all and I'm not sure that PHPExcel
461
                            //          has an API for this)
462
463
//                            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...
464
//                                $rowData = $cellData;
465
//                                break;
466
//                            }
467
                            break;
468 7
                        case 'table-row':
469 7
                            if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) {
470 7
                                $rowRepeats = $childNode->getAttributeNS($tableNs, 'number-rows-repeated');
471
                            } else {
472 7
                                $rowRepeats = 1;
473
                            }
474
475 7
                            $columnID = 'A';
476 7
                            foreach ($childNode->childNodes as $key => $cellData) {
477
                                /* @var \DOMElement $cellData */
478
479 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...
480 7
                                    if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
481
                                        ++$columnID;
482
                                        continue;
483
                                    }
484
                                }
485
486
                                // Initialize variables
487 7
                                $formatting = $hyperlink = null;
488 7
                                $hasCalculatedValue = false;
489 7
                                $cellDataFormula = '';
490
491 7
                                if ($cellData->hasAttributeNS($tableNs, 'formula')) {
492 5
                                    $cellDataFormula = $cellData->getAttributeNS($tableNs, 'formula');
493 5
                                    $hasCalculatedValue = true;
494
                                }
495
496
                                // Annotations
497 7
                                $annotation = $cellData->getElementsByTagNameNS($officeNs, 'annotation');
498
499 7
                                if ($annotation->length > 0) {
500 5
                                    $textNode = $annotation->item(0)->getElementsByTagNameNS($textNs, 'p');
501
502 5
                                    if ($textNode->length > 0) {
503 5
                                        $text = $this->scanElementForText($textNode->item(0));
504
505 5
                                        $spreadsheet->getActiveSheet()
506 5
                                            ->getComment($columnID . $rowID)
507 5
                                            ->setText($this->parseRichText($text));
508
//                                                                    ->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...
509
                                    }
510
                                }
511
512
                                // Content
513
514
                                /** @var \DOMElement[] $paragraphs */
515 7
                                $paragraphs = [];
516
517 7
                                foreach ($cellData->childNodes as $item) {
518
                                    /** @var \DOMElement $item */
519
520
                                    // Filter text:p elements
521 7
                                    if ($item->nodeName == 'text:p') {
522 7
                                        $paragraphs[] = $item;
523
                                    }
524
                                }
525
526 7
                                if (count($paragraphs) > 0) {
527
                                    // Consolidate if there are multiple p records (maybe with spans as well)
528 7
                                    $dataArray = [];
529
530
                                    // Text can have multiple text:p and within those, multiple text:span.
531
                                    // text:p newlines, but text:span does not.
532
                                    // Also, here we assume there is no text data is span fields are specified, since
533
                                    // we have no way of knowing proper positioning anyway.
534
535 7
                                    foreach ($paragraphs as $pData) {
536 7
                                        $dataArray[] = $this->scanElementForText($pData);
537
                                    }
538 7
                                    $allCellDataText = implode($dataArray, "\n");
539
540 7
                                    $type = $cellData->getAttributeNS($officeNs, 'value-type');
541
542
                                    switch ($type) {
543 7
                                        case 'string':
544 7
                                            $type = DataType::TYPE_STRING;
545 7
                                            $dataValue = $allCellDataText;
546
547 7
                                            foreach ($paragraphs as $paragraph) {
548 7
                                                $link = $paragraph->getElementsByTagNameNS($textNs, 'a');
549 7
                                                if ($link->length > 0) {
550 5
                                                    $hyperlink = $link->item(0)->getAttributeNS($xlinkNs, 'href');
551
                                                }
552
                                            }
553
554 7
                                            break;
555 7
                                        case 'boolean':
556 5
                                            $type = DataType::TYPE_BOOL;
557 5
                                            $dataValue = ($allCellDataText == 'TRUE') ? true : false;
558 5
                                            break;
559 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...
560 3
                                            $type = DataType::TYPE_NUMERIC;
561 3
                                            $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
562
563 3
                                            if (floor($dataValue) == $dataValue) {
564
                                                $dataValue = (int) $dataValue;
565
                                            }
566 3
                                            $formatting = \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_PERCENTAGE_00;
567 3
                                            break;
568 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...
569 3
                                            $type = DataType::TYPE_NUMERIC;
570 3
                                            $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
571
572 3
                                            if (floor($dataValue) == $dataValue) {
573 3
                                                $dataValue = (int) $dataValue;
574
                                            }
575 3
                                            $formatting = \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_CURRENCY_USD_SIMPLE;
576 3
                                            break;
577 5
                                        case 'float':
578 5
                                            $type = DataType::TYPE_NUMERIC;
579 5
                                            $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
580
581 5
                                            if (floor($dataValue) == $dataValue) {
582 5
                                                if ($dataValue == (int) $dataValue) {
583 5
                                                    $dataValue = (int) $dataValue;
584
                                                } else {
585
                                                    $dataValue = (float) $dataValue;
586
                                                }
587
                                            }
588 5
                                            break;
589 5
                                        case 'date':
590 5
                                            $type = DataType::TYPE_NUMERIC;
591 5
                                            $value = $cellData->getAttributeNS($officeNs, 'date-value');
592
593 5
                                            $dateObj = new DateTime($value, $GMT);
594 5
                                            $dateObj->setTimeZone($timezoneObj);
595 5
                                            list($year, $month, $day, $hour, $minute, $second) = explode(
596 5
                                                ' ',
597 5
                                                $dateObj->format('Y m d H i s')
598
                                            );
599
600 5
                                            $dataValue = \PhpOffice\PhpSpreadsheet\Shared\Date::formattedPHPToExcel(
601
                                                $year,
602
                                                $month,
603
                                                $day,
604
                                                $hour,
605
                                                $minute,
606
                                                $second
607
                                            );
608
609 5
                                            if ($dataValue != floor($dataValue)) {
610 5
                                                $formatting = \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_XLSX15
611
                                                    . ' '
612 5
                                                    . \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_TIME4;
613
                                            } else {
614 5
                                                $formatting = \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_XLSX15;
615
                                            }
616 5
                                            break;
617 5
                                        case 'time':
618 5
                                            $type = DataType::TYPE_NUMERIC;
619
620 5
                                            $timeValue = $cellData->getAttributeNS($officeNs, 'time-value');
621
622 5
                                            $dataValue = \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel(
623
                                                strtotime(
624 5
                                                    '01-01-1970 ' . implode(':', sscanf($timeValue, 'PT%dH%dM%dS'))
625
                                                )
626
                                            );
627 5
                                            $formatting = \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_TIME4;
628 5
                                            break;
629
                                        default:
630
                                            $dataValue = null;
631
                                    }
632
                                } else {
633 7
                                    $type = DataType::TYPE_NULL;
634 7
                                    $dataValue = null;
635
                                }
636
637 7
                                if ($hasCalculatedValue) {
638 5
                                    $type = DataType::TYPE_FORMULA;
639 5
                                    $cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1);
640 5
                                    $temp = explode('"', $cellDataFormula);
641 5
                                    $tKey = false;
642 5
                                    foreach ($temp as &$value) {
643
                                        // Only replace in alternate array entries (i.e. non-quoted blocks)
644 5
                                        if ($tKey = !$tKey) {
645
                                            // Cell range reference in another sheet
646 5
                                            $value = preg_replace('/\[([^\.]+)\.([^\.]+):\.([^\.]+)\]/Ui', '$1!$2:$3', $value);
647
648
                                            // Cell reference in another sheet
649 5
                                            $value = preg_replace('/\[([^\.]+)\.([^\.]+)\]/Ui', '$1!$2', $value);
650
651
                                            // Cell range reference
652 5
                                            $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/Ui', '$1:$2', $value);
653
654
                                            // Simple cell reference
655 5
                                            $value = preg_replace('/\[\.([^\.]+)\]/Ui', '$1', $value);
656
657 5
                                            $value = Calculation::translateSeparator(';', ',', $value, $inBraces);
658
                                        }
659
                                    }
660 5
                                    unset($value);
661
662
                                    // Then rebuild the formula string
663 5
                                    $cellDataFormula = implode('"', $temp);
664
                                }
665
666 7
                                if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
667 7
                                    $colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
668
                                } else {
669 7
                                    $colRepeats = 1;
670
                                }
671
672 7
                                if ($type !== null) {
673 7
                                    for ($i = 0; $i < $colRepeats; ++$i) {
674 7
                                        if ($i > 0) {
675 7
                                            ++$columnID;
676
                                        }
677
678 7
                                        if ($type !== DataType::TYPE_NULL) {
679 7
                                            for ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) {
680 7
                                                $rID = $rowID + $rowAdjust;
681
682 7
                                                $cell = $spreadsheet->getActiveSheet()
683 7
                                                            ->getCell($columnID . $rID);
684
685
                                                // Set value
686 7
                                                if ($hasCalculatedValue) {
687 5
                                                    $cell->setValueExplicit($cellDataFormula, $type);
688
                                                } else {
689 7
                                                    $cell->setValueExplicit($dataValue, $type);
690
                                                }
691
692 7
                                                if ($hasCalculatedValue) {
693 5
                                                    $cell->setCalculatedValue($dataValue);
694
                                                }
695
696
                                                // Set other properties
697 7
                                                if ($formatting !== null) {
698 7
                                                    $spreadsheet->getActiveSheet()
699 7
                                                        ->getStyle($columnID . $rID)
700 7
                                                        ->getNumberFormat()
701 7
                                                        ->setFormatCode($formatting);
702
                                                } else {
703 7
                                                    $spreadsheet->getActiveSheet()
704 7
                                                        ->getStyle($columnID . $rID)
705 7
                                                        ->getNumberFormat()
706 7
                                                        ->setFormatCode(NumberFormat::FORMAT_GENERAL);
707
                                                }
708
709 7
                                                if ($hyperlink !== null) {
710 5
                                                    $cell->getHyperlink()
711 5
                                                        ->setUrl($hyperlink);
712
                                                }
713
                                            }
714
                                        }
715
                                    }
716
                                }
717
718
                                // Merged cells
719 7
                                if ($childNode->hasAttributeNS($tableNs, 'number-columns-spanned')
720 7
                                    || $childNode->hasAttributeNS($tableNs, 'number-rows-spanned')
721
                                ) {
722
                                    if (($type !== DataType::TYPE_NULL) || (!$this->readDataOnly)) {
723
                                        $columnTo = $columnID;
724
725
                                        if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) {
726
                                            $columnIndex = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($columnID);
727
                                            $columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned');
728
                                            $columnIndex -= 2;
729
730
                                            $columnTo = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($columnIndex);
731
                                        }
732
733
                                        $rowTo = $rowID;
734
735
                                        if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) {
736
                                            $rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1;
737
                                        }
738
739
                                        $cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo;
740
                                        $spreadsheet->getActiveSheet()->mergeCells($cellRange);
741
                                    }
742
                                }
743
744 7
                                ++$columnID;
745
                            }
746 7
                            $rowID += $rowRepeats;
747 7
                            break;
748
                    }
749
                }
750 7
                ++$worksheetID;
751
            }
752
        }
753
754
        // Return
755 7
        return $spreadsheet;
756
    }
757
758
    /**
759
     * Recursively scan element.
760
     *
761
     * @param \DOMNode $element
762
     *
763
     * @return string
764
     */
765 7
    protected function scanElementForText(\DOMNode $element)
766
    {
767 7
        $str = '';
768 7
        foreach ($element->childNodes as $child) {
769
            /** @var \DOMNode $child */
770 7
            if ($child->nodeType == XML_TEXT_NODE) {
771 7
                $str .= $child->nodeValue;
772 7
            } elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:s') {
773
                // It's a space
774
775
                // Multiple spaces?
776 3
                if (isset($child->attributes['text:c'])) {
777
                    /** @var \DOMAttr $cAttr */
778 3
                    $cAttr = $child->attributes['text:c'];
779 3
                    $multiplier = (int) $cAttr->nodeValue;
780
                } else {
781 3
                    $multiplier = 1;
782
                }
783
784 3
                $str .= str_repeat(' ', $multiplier);
785
            }
786
787 7
            if ($child->hasChildNodes()) {
788 7
                $str .= $this->scanElementForText($child);
789
            }
790
        }
791
792 7
        return $str;
793
    }
794
795
    /**
796
     * @param string $is
797
     *
798
     * @return \PhpOffice\PhpSpreadsheet\RichText
799
     */
800 5
    private function parseRichText($is = '')
801
    {
802 5
        $value = new \PhpOffice\PhpSpreadsheet\RichText();
803 5
        $value->createText($is);
804
805 5
        return $value;
806
    }
807
}
808