Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Ods often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Ods, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
34 | class Ods extends BaseReader implements IReader |
||
35 | { |
||
36 | /** |
||
37 | * Formats. |
||
38 | * |
||
39 | * @var array |
||
40 | */ |
||
41 | private $styles = []; |
||
|
|||
42 | |||
43 | /** |
||
44 | * Create a new Ods Reader instance. |
||
45 | */ |
||
46 | 9 | public function __construct() |
|
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')) { |
||
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( |
||
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'); |
||
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) |
|
273 | |||
274 | View Code Duplication | private static function identifyFixedStyleValue($styleList, &$styleAttributeValue) |
|
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); |
|
347 | 7 | $docProps->setModified($creationDate); |
|
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); |
|
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); |
|
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) { |
||
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) { |
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 ) |
||
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': |
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': |
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) |
|
805 | |||
806 | /** |
||
807 | * @param string $is |
||
808 | * |
||
809 | * @return \PhpOffice\PhpSpreadsheet\RichText |
||
810 | */ |
||
811 | 5 | private function parseRichText($is = '') |
|
818 | } |
||
819 |
This check marks private properties in classes that are never used. Those properties can be removed.