ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Ods.php
Go to the documentation of this file.
1<?php
2
4
5use DateTime;
6use DOMAttr;
7use DOMDocument;
8use DOMElement;
9use DOMNode;
25use Throwable;
26use XMLReader;
27use ZipArchive;
28
29class Ods extends BaseReader
30{
34 public function __construct()
35 {
36 parent::__construct();
37 $this->securityScanner = XmlScanner::getInstance($this);
38 }
39
47 public function canRead($pFilename)
48 {
49 File::assertFile($pFilename);
50
51 $mimeType = 'UNKNOWN';
52
53 // Load file
54
55 $zip = new ZipArchive();
56 if ($zip->open($pFilename) === true) {
57 // check if it is an OOXML archive
58 $stat = $zip->statName('mimetype');
59 if ($stat && ($stat['size'] <= 255)) {
60 $mimeType = $zip->getFromName($stat['name']);
61 } elseif ($zip->statName('META-INF/manifest.xml')) {
62 $xml = simplexml_load_string(
63 $this->securityScanner->scan($zip->getFromName('META-INF/manifest.xml')),
64 'SimpleXMLElement',
66 );
67 $namespacesContent = $xml->getNamespaces(true);
68 if (isset($namespacesContent['manifest'])) {
69 $manifest = $xml->children($namespacesContent['manifest']);
70 foreach ($manifest as $manifestDataSet) {
71 $manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']);
72 if ($manifestAttributes->{'full-path'} == '/') {
73 $mimeType = (string) $manifestAttributes->{'media-type'};
74
75 break;
76 }
77 }
78 }
79 }
80
81 $zip->close();
82 }
83
84 return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet';
85 }
86
94 public function listWorksheetNames($pFilename)
95 {
96 File::assertFile($pFilename);
97
98 $zip = new ZipArchive();
99 if ($zip->open($pFilename) !== true) {
100 throw new ReaderException('Could not open ' . $pFilename . ' for reading! Error opening file.');
101 }
102
103 $worksheetNames = [];
104
105 $xml = new XMLReader();
106 $xml->xml(
107 $this->securityScanner->scanFile('zip://' . realpath($pFilename) . '#content.xml'),
108 null,
109 Settings::getLibXmlLoaderOptions()
110 );
111 $xml->setParserProperty(2, true);
112
113 // Step into the first level of content of the XML
114 $xml->read();
115 while ($xml->read()) {
116 // Quickly jump through to the office:body node
117 while ($xml->name !== 'office:body') {
118 if ($xml->isEmptyElement) {
119 $xml->read();
120 } else {
121 $xml->next();
122 }
123 }
124 // Now read each node until we find our first table:table node
125 while ($xml->read()) {
126 if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
127 // Loop through each table:table node reading the table:name attribute for each worksheet name
128 do {
129 $worksheetNames[] = $xml->getAttribute('table:name');
130 $xml->next();
131 } while ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT);
132 }
133 }
134 }
135
136 return $worksheetNames;
137 }
138
146 public function listWorksheetInfo($pFilename)
147 {
148 File::assertFile($pFilename);
149
150 $worksheetInfo = [];
151
152 $zip = new ZipArchive();
153 if ($zip->open($pFilename) !== true) {
154 throw new ReaderException('Could not open ' . $pFilename . ' for reading! Error opening file.');
155 }
156
157 $xml = new XMLReader();
158 $xml->xml(
159 $this->securityScanner->scanFile('zip://' . realpath($pFilename) . '#content.xml'),
160 null,
161 Settings::getLibXmlLoaderOptions()
162 );
163 $xml->setParserProperty(2, true);
164
165 // Step into the first level of content of the XML
166 $xml->read();
167 while ($xml->read()) {
168 // Quickly jump through to the office:body node
169 while ($xml->name !== 'office:body') {
170 if ($xml->isEmptyElement) {
171 $xml->read();
172 } else {
173 $xml->next();
174 }
175 }
176 // Now read each node until we find our first table:table node
177 while ($xml->read()) {
178 if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
179 $worksheetNames[] = $xml->getAttribute('table:name');
180
181 $tmpInfo = [
182 'worksheetName' => $xml->getAttribute('table:name'),
183 'lastColumnLetter' => 'A',
184 'lastColumnIndex' => 0,
185 'totalRows' => 0,
186 'totalColumns' => 0,
187 ];
188
189 // Loop through each child node of the table:table element reading
190 $currCells = 0;
191 do {
192 $xml->read();
193 if ($xml->name == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) {
194 $rowspan = $xml->getAttribute('table:number-rows-repeated');
195 $rowspan = empty($rowspan) ? 1 : $rowspan;
196 $tmpInfo['totalRows'] += $rowspan;
197 $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
198 $currCells = 0;
199 // Step into the row
200 $xml->read();
201 do {
202 $doread = true;
203 if ($xml->name == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
204 if (!$xml->isEmptyElement) {
205 ++$currCells;
206 $xml->next();
207 $doread = false;
208 }
209 } elseif ($xml->name == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
210 $mergeSize = $xml->getAttribute('table:number-columns-repeated');
211 $currCells += (int) $mergeSize;
212 }
213 if ($doread) {
214 $xml->read();
215 }
216 } while ($xml->name != 'table:table-row');
217 }
218 } while ($xml->name != 'table:table');
219
220 $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
221 $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
222 $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
223 $worksheetInfo[] = $tmpInfo;
224 }
225 }
226 }
227
228 return $worksheetInfo;
229 }
230
238 public function load($pFilename)
239 {
240 // Create new Spreadsheet
241 $spreadsheet = new Spreadsheet();
242
243 // Load into this instance
244 return $this->loadIntoExisting($pFilename, $spreadsheet);
245 }
246
254 public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
255 {
256 File::assertFile($pFilename);
257
258 $zip = new ZipArchive();
259 if ($zip->open($pFilename) !== true) {
260 throw new Exception("Could not open {$pFilename} for reading! Error opening file.");
261 }
262
263 // Meta
264
265 $xml = @simplexml_load_string(
266 $this->securityScanner->scan($zip->getFromName('meta.xml')),
267 'SimpleXMLElement',
268 Settings::getLibXmlLoaderOptions()
269 );
270 if ($xml === false) {
271 throw new Exception('Unable to read data from {$pFilename}');
272 }
273
274 $namespacesMeta = $xml->getNamespaces(true);
275
276 (new DocumentProperties($spreadsheet))->load($xml, $namespacesMeta);
277
278 // Styles
279
280 $dom = new DOMDocument('1.01', 'UTF-8');
281 $dom->loadXML(
282 $this->securityScanner->scan($zip->getFromName('styles.xml')),
283 Settings::getLibXmlLoaderOptions()
284 );
285
286 $pageSettings = new PageSettings($dom);
287
288 // Main Content
289
290 $dom = new DOMDocument('1.01', 'UTF-8');
291 $dom->loadXML(
292 $this->securityScanner->scan($zip->getFromName('content.xml')),
293 Settings::getLibXmlLoaderOptions()
294 );
295
296 $officeNs = $dom->lookupNamespaceUri('office');
297 $tableNs = $dom->lookupNamespaceUri('table');
298 $textNs = $dom->lookupNamespaceUri('text');
299 $xlinkNs = $dom->lookupNamespaceUri('xlink');
300
301 $pageSettings->readStyleCrossReferences($dom);
302
303 $autoFilterReader = new AutoFilter($spreadsheet, $tableNs);
304 $definedNameReader = new DefinedNames($spreadsheet, $tableNs);
305
306 // Content
307 $spreadsheets = $dom->getElementsByTagNameNS($officeNs, 'body')
308 ->item(0)
309 ->getElementsByTagNameNS($officeNs, 'spreadsheet');
310
311 foreach ($spreadsheets as $workbookData) {
313 $tables = $workbookData->getElementsByTagNameNS($tableNs, 'table');
314
315 $worksheetID = 0;
316 foreach ($tables as $worksheetDataSet) {
318 $worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name');
319
320 // Check loadSheetsOnly
321 if (
322 isset($this->loadSheetsOnly)
323 && $worksheetName
324 && !in_array($worksheetName, $this->loadSheetsOnly)
325 ) {
326 continue;
327 }
328
329 $worksheetStyleName = $worksheetDataSet->getAttributeNS($tableNs, 'style-name');
330
331 // Create sheet
332 if ($worksheetID > 0) {
333 $spreadsheet->createSheet(); // First sheet is added by default
334 }
335 $spreadsheet->setActiveSheetIndex($worksheetID);
336
337 if ($worksheetName) {
338 // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
339 // formula cells... during the load, all formulae should be correct, and we're simply
340 // bringing the worksheet name in line with the formula, not the reverse
341 $spreadsheet->getActiveSheet()->setTitle((string) $worksheetName, false, false);
342 }
343
344 // Go through every child of table element
345 $rowID = 1;
346 foreach ($worksheetDataSet->childNodes as $childNode) {
349 // Filter elements which are not under the "table" ns
350 if ($childNode->namespaceURI != $tableNs) {
351 continue;
352 }
353
354 $key = $childNode->nodeName;
355
356 // Remove ns from node name
357 if (strpos($key, ':') !== false) {
358 $keyChunks = explode(':', $key);
359 $key = array_pop($keyChunks);
360 }
361
362 switch ($key) {
363 case 'table-header-rows':
365 // ($rowData it's not used at all and I'm not sure that PHPExcel
366 // has an API for this)
367
368// foreach ($rowData as $keyRowData => $cellData) {
369// $rowData = $cellData;
370// break;
371// }
372 break;
373 case 'table-row':
374 if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) {
375 $rowRepeats = $childNode->getAttributeNS($tableNs, 'number-rows-repeated');
376 } else {
377 $rowRepeats = 1;
378 }
379
380 $columnID = 'A';
382 foreach ($childNode->childNodes as $cellData) {
383 if ($this->getReadFilter() !== null) {
384 if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
385 ++$columnID;
386
387 continue;
388 }
389 }
390
391 // Initialize variables
392 $formatting = $hyperlink = null;
393 $hasCalculatedValue = false;
394 $cellDataFormula = '';
395
396 if ($cellData->hasAttributeNS($tableNs, 'formula')) {
397 $cellDataFormula = $cellData->getAttributeNS($tableNs, 'formula');
398 $hasCalculatedValue = true;
399 }
400
401 // Annotations
402 $annotation = $cellData->getElementsByTagNameNS($officeNs, 'annotation');
403
404 if ($annotation->length > 0) {
405 $textNode = $annotation->item(0)->getElementsByTagNameNS($textNs, 'p');
406
407 if ($textNode->length > 0) {
408 $text = $this->scanElementForText($textNode->item(0));
409
410 $spreadsheet->getActiveSheet()
411 ->getComment($columnID . $rowID)
412 ->setText($this->parseRichText($text));
413// ->setAuthor( $author )
414 }
415 }
416
417 // Content
418
420 $paragraphs = [];
421
422 foreach ($cellData->childNodes as $item) {
425 // Filter text:p elements
426 if ($item->nodeName == 'text:p') {
427 $paragraphs[] = $item;
428 }
429 }
430
431 if (count($paragraphs) > 0) {
432 // Consolidate if there are multiple p records (maybe with spans as well)
433 $dataArray = [];
434
435 // Text can have multiple text:p and within those, multiple text:span.
436 // text:p newlines, but text:span does not.
437 // Also, here we assume there is no text data is span fields are specified, since
438 // we have no way of knowing proper positioning anyway.
439
440 foreach ($paragraphs as $pData) {
441 $dataArray[] = $this->scanElementForText($pData);
442 }
443 $allCellDataText = implode("\n", $dataArray);
444
445 $type = $cellData->getAttributeNS($officeNs, 'value-type');
446
447 switch ($type) {
448 case 'string':
449 $type = DataType::TYPE_STRING;
450 $dataValue = $allCellDataText;
451
452 foreach ($paragraphs as $paragraph) {
453 $link = $paragraph->getElementsByTagNameNS($textNs, 'a');
454 if ($link->length > 0) {
455 $hyperlink = $link->item(0)->getAttributeNS($xlinkNs, 'href');
456 }
457 }
458
459 break;
460 case 'boolean':
461 $type = DataType::TYPE_BOOL;
462 $dataValue = ($allCellDataText == 'TRUE') ? true : false;
463
464 break;
465 case 'percentage':
466 $type = DataType::TYPE_NUMERIC;
467 $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
468
469 // percentage should always be float
470 //if (floor($dataValue) == $dataValue) {
471 // $dataValue = (int) $dataValue;
472 //}
473 $formatting = NumberFormat::FORMAT_PERCENTAGE_00;
474
475 break;
476 case 'currency':
477 $type = DataType::TYPE_NUMERIC;
478 $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
479
480 if (floor($dataValue) == $dataValue) {
481 $dataValue = (int) $dataValue;
482 }
483 $formatting = NumberFormat::FORMAT_CURRENCY_USD_SIMPLE;
484
485 break;
486 case 'float':
487 $type = DataType::TYPE_NUMERIC;
488 $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
489
490 if (floor($dataValue) == $dataValue) {
491 if ($dataValue == (int) $dataValue) {
492 $dataValue = (int) $dataValue;
493 }
494 }
495
496 break;
497 case 'date':
498 $type = DataType::TYPE_NUMERIC;
499 $value = $cellData->getAttributeNS($officeNs, 'date-value');
500
501 $dateObj = new DateTime($value);
502 [$year, $month, $day, $hour, $minute, $second] = explode(
503 ' ',
504 $dateObj->format('Y m d H i s')
505 );
506
507 $dataValue = Date::formattedPHPToExcel(
508 (int) $year,
509 (int) $month,
510 (int) $day,
511 (int) $hour,
512 (int) $minute,
513 (int) $second
514 );
515
516 if ($dataValue != floor($dataValue)) {
517 $formatting = NumberFormat::FORMAT_DATE_XLSX15
518 . ' '
519 . NumberFormat::FORMAT_DATE_TIME4;
520 } else {
521 $formatting = NumberFormat::FORMAT_DATE_XLSX15;
522 }
523
524 break;
525 case 'time':
526 $type = DataType::TYPE_NUMERIC;
527
528 $timeValue = $cellData->getAttributeNS($officeNs, 'time-value');
529
530 $dataValue = Date::PHPToExcel(
531 strtotime(
532 '01-01-1970 ' . implode(':', sscanf($timeValue, 'PT%dH%dM%dS'))
533 )
534 );
535 $formatting = NumberFormat::FORMAT_DATE_TIME4;
536
537 break;
538 default:
539 $dataValue = null;
540 }
541 } else {
542 $type = DataType::TYPE_NULL;
543 $dataValue = null;
544 }
545
546 if ($hasCalculatedValue) {
547 $type = DataType::TYPE_FORMULA;
548 $cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1);
549 $cellDataFormula = $this->convertToExcelFormulaValue($cellDataFormula);
550 }
551
552 if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
553 $colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
554 } else {
555 $colRepeats = 1;
556 }
557
558 if ($type !== null) {
559 for ($i = 0; $i < $colRepeats; ++$i) {
560 if ($i > 0) {
561 ++$columnID;
562 }
563
564 if ($type !== DataType::TYPE_NULL) {
565 for ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) {
566 $rID = $rowID + $rowAdjust;
567
568 $cell = $spreadsheet->getActiveSheet()
569 ->getCell($columnID . $rID);
570
571 // Set value
572 if ($hasCalculatedValue) {
573 $cell->setValueExplicit($cellDataFormula, $type);
574 } else {
575 $cell->setValueExplicit($dataValue, $type);
576 }
577
578 if ($hasCalculatedValue) {
579 $cell->setCalculatedValue($dataValue);
580 }
581
582 // Set other properties
583 if ($formatting !== null) {
584 $spreadsheet->getActiveSheet()
585 ->getStyle($columnID . $rID)
586 ->getNumberFormat()
587 ->setFormatCode($formatting);
588 } else {
589 $spreadsheet->getActiveSheet()
590 ->getStyle($columnID . $rID)
591 ->getNumberFormat()
592 ->setFormatCode(NumberFormat::FORMAT_GENERAL);
593 }
594
595 if ($hyperlink !== null) {
596 $cell->getHyperlink()
597 ->setUrl($hyperlink);
598 }
599 }
600 }
601 }
602 }
603
604 // Merged cells
605 if (
606 $cellData->hasAttributeNS($tableNs, 'number-columns-spanned')
607 || $cellData->hasAttributeNS($tableNs, 'number-rows-spanned')
608 ) {
609 if (($type !== DataType::TYPE_NULL) || (!$this->readDataOnly)) {
610 $columnTo = $columnID;
611
612 if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) {
613 $columnIndex = Coordinate::columnIndexFromString($columnID);
614 $columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned');
615 $columnIndex -= 2;
616
617 $columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1);
618 }
619
620 $rowTo = $rowID;
621
622 if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) {
623 $rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1;
624 }
625
626 $cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo;
627 $spreadsheet->getActiveSheet()->mergeCells($cellRange);
628 }
629 }
630
631 ++$columnID;
632 }
633 $rowID += $rowRepeats;
634
635 break;
636 }
637 }
638 $pageSettings->setPrintSettingsForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName);
639 ++$worksheetID;
640 }
641
642 $autoFilterReader->read($workbookData);
643 $definedNameReader->read($workbookData);
644 }
645 $spreadsheet->setActiveSheetIndex(0);
646
647 if ($zip->locateName('settings.xml') !== false) {
648 $this->processSettings($zip, $spreadsheet);
649 }
650 // Return
651 return $spreadsheet;
652 }
653
654 private function processSettings(ZipArchive $zip, Spreadsheet $spreadsheet): void
655 {
656 $dom = new DOMDocument('1.01', 'UTF-8');
657 $dom->loadXML(
658 $this->securityScanner->scan($zip->getFromName('settings.xml')),
659 Settings::getLibXmlLoaderOptions()
660 );
661 //$xlinkNs = $dom->lookupNamespaceUri('xlink');
662 $configNs = $dom->lookupNamespaceUri('config');
663 //$oooNs = $dom->lookupNamespaceUri('ooo');
664 $officeNs = $dom->lookupNamespaceUri('office');
665 $settings = $dom->getElementsByTagNameNS($officeNs, 'settings')
666 ->item(0);
667 $this->lookForActiveSheet($settings, $spreadsheet, $configNs);
668 $this->lookForSelectedCells($settings, $spreadsheet, $configNs);
669 }
670
671 private function lookForActiveSheet(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void
672 {
674 foreach ($settings->getElementsByTagNameNS($configNs, 'config-item') as $t) {
675 if ($t->getAttributeNs($configNs, 'name') === 'ActiveTable') {
676 try {
677 $spreadsheet->setActiveSheetIndexByName($t->nodeValue);
678 } catch (Throwable $e) {
679 // do nothing
680 }
681
682 break;
683 }
684 }
685 }
686
687 private function lookForSelectedCells(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void
688 {
690 foreach ($settings->getElementsByTagNameNS($configNs, 'config-item-map-named') as $t) {
691 if ($t->getAttributeNs($configNs, 'name') === 'Tables') {
692 foreach ($t->getElementsByTagNameNS($configNs, 'config-item-map-entry') as $ws) {
693 $setRow = $setCol = '';
694 $wsname = $ws->getAttributeNs($configNs, 'name');
695 foreach ($ws->getElementsByTagNameNS($configNs, 'config-item') as $configItem) {
696 $attrName = $configItem->getAttributeNs($configNs, 'name');
697 if ($attrName === 'CursorPositionX') {
698 $setCol = $configItem->nodeValue;
699 }
700 if ($attrName === 'CursorPositionY') {
701 $setRow = $configItem->nodeValue;
702 }
703 }
704 $this->setSelected($spreadsheet, $wsname, $setCol, $setRow);
705 }
706
707 break;
708 }
709 }
710 }
711
712 private function setSelected(Spreadsheet $spreadsheet, string $wsname, string $setCol, string $setRow): void
713 {
714 if (is_numeric($setCol) && is_numeric($setRow)) {
715 try {
716 $spreadsheet->getSheetByName($wsname)->setSelectedCellByColumnAndRow($setCol + 1, $setRow + 1);
717 } catch (Throwable $e) {
718 // do nothing
719 }
720 }
721 }
722
728 protected function scanElementForText(DOMNode $element)
729 {
730 $str = '';
731 foreach ($element->childNodes as $child) {
733 if ($child->nodeType == XML_TEXT_NODE) {
734 $str .= $child->nodeValue;
735 } elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:s') {
736 // It's a space
737
738 // Multiple spaces?
740 $cAttr = $child->attributes->getNamedItem('c');
741 if ($cAttr) {
742 $multiplier = (int) $cAttr->nodeValue;
743 } else {
744 $multiplier = 1;
745 }
746
747 $str .= str_repeat(' ', $multiplier);
748 }
749
750 if ($child->hasChildNodes()) {
751 $str .= $this->scanElementForText($child);
752 }
753 }
754
755 return $str;
756 }
757
763 private function parseRichText($is)
764 {
765 $value = new RichText();
766 $value->createText($is);
767
768 return $value;
769 }
770
771 private function convertToExcelFormulaValue(string $openOfficeFormula): string
772 {
773 $temp = explode('"', $openOfficeFormula);
774 $tKey = false;
775 foreach ($temp as &$value) {
776 // Only replace in alternate array entries (i.e. non-quoted blocks)
777 if ($tKey = !$tKey) {
778 // Cell range reference in another sheet
779 $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', '$1!$2:$3', $value);
780 // Cell reference in another sheet
781 $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+)\]/miu', '$1!$2', $value ?? '');
782 // Cell range reference
783 $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/miu', '$1:$2', $value ?? '');
784 // Simple cell reference
785 $value = preg_replace('/\[\.([^\.]+)\]/miu', '$1', $value ?? '');
786 // Convert references to defined names/formulae
787 $value = str_replace('$$', '', $value ?? '');
788
789 $value = Calculation::translateSeparator(';', ',', $value, $inBraces);
790 }
791 }
792
793 // Then rebuild the formula string
794 $excelFormula = implode('"', $temp);
795
796 return $excelFormula;
797 }
798}
An exception for terminatinating execution or to throw for unit testing.
Helper class to manipulate cell coordinates.
Definition: Coordinate.php:15
listWorksheetInfo($pFilename)
Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
Definition: Ods.php:146
load($pFilename)
Loads PhpSpreadsheet from file.
Definition: Ods.php:238
__construct()
Create a new Ods Reader instance.
Definition: Ods.php:34
convertToExcelFormulaValue(string $openOfficeFormula)
Definition: Ods.php:771
processSettings(ZipArchive $zip, Spreadsheet $spreadsheet)
Definition: Ods.php:654
listWorksheetNames($pFilename)
Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
Definition: Ods.php:94
canRead($pFilename)
Can the current IReader read the file?
Definition: Ods.php:47
setSelected(Spreadsheet $spreadsheet, string $wsname, string $setCol, string $setRow)
Definition: Ods.php:712
static getInstance(Reader\IReader $reader)
Definition: XmlScanner.php:39
static getLibXmlLoaderOptions()
Get default options for libxml loader.
Definition: Settings.php:116
static assertFile($filename)
Assert that given path is an existing file and is readable, otherwise throw exception.
Definition: File.php:143
createSheet($sheetIndex=null)
Create sheet and add it to this workbook.
setActiveSheetIndex($pIndex)
Set active sheet index.
getSheetByName($pName)
Get sheet by name.
setActiveSheetIndexByName($pValue)
Set active sheet index by name.
$key
Definition: croninfo.php:18
$i
Definition: disco.tpl.php:19
$type
$text
Definition: errorreport.php:18