36 parent::__construct();
51 $mimeType =
'UNKNOWN';
56 if ($zip->open($pFilename) ===
true) {
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')),
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'};
84 return $mimeType ===
'application/vnd.oasis.opendocument.spreadsheet';
99 if ($zip->open($pFilename) !==
true) {
100 throw new ReaderException(
'Could not open ' . $pFilename .
' for reading! Error opening file.');
103 $worksheetNames = [];
107 $this->securityScanner->scanFile(
'zip://' . realpath($pFilename) .
'#content.xml'),
111 $xml->setParserProperty(2,
true);
115 while (
$xml->read()) {
117 while (
$xml->name !==
'office:body') {
118 if (
$xml->isEmptyElement) {
125 while (
$xml->read()) {
126 if (
$xml->name ==
'table:table' &&
$xml->nodeType == XMLReader::ELEMENT) {
129 $worksheetNames[] =
$xml->getAttribute(
'table:name');
131 }
while (
$xml->name ==
'table:table' &&
$xml->nodeType == XMLReader::ELEMENT);
136 return $worksheetNames;
153 if ($zip->open($pFilename) !==
true) {
154 throw new ReaderException(
'Could not open ' . $pFilename .
' for reading! Error opening file.');
159 $this->securityScanner->scanFile(
'zip://' . realpath($pFilename) .
'#content.xml'),
163 $xml->setParserProperty(2,
true);
167 while (
$xml->read()) {
169 while (
$xml->name !==
'office:body') {
170 if (
$xml->isEmptyElement) {
177 while (
$xml->read()) {
178 if (
$xml->name ==
'table:table' &&
$xml->nodeType == XMLReader::ELEMENT) {
179 $worksheetNames[] =
$xml->getAttribute(
'table:name');
182 'worksheetName' =>
$xml->getAttribute(
'table:name'),
183 'lastColumnLetter' =>
'A',
184 'lastColumnIndex' => 0,
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);
203 if (
$xml->name ==
'table:table-cell' &&
$xml->nodeType == XMLReader::ELEMENT) {
204 if (!
$xml->isEmptyElement) {
209 } elseif (
$xml->name ==
'table:covered-table-cell' &&
$xml->nodeType == XMLReader::ELEMENT) {
210 $mergeSize =
$xml->getAttribute(
'table:number-columns-repeated');
211 $currCells += (int) $mergeSize;
216 }
while (
$xml->name !=
'table:table-row');
218 }
while (
$xml->name !=
'table:table');
220 $tmpInfo[
'totalColumns'] = max($tmpInfo[
'totalColumns'], $currCells);
221 $tmpInfo[
'lastColumnIndex'] = $tmpInfo[
'totalColumns'] - 1;
223 $worksheetInfo[] = $tmpInfo;
228 return $worksheetInfo;
238 public function load($pFilename)
244 return $this->loadIntoExisting($pFilename, $spreadsheet);
254 public function loadIntoExisting($pFilename,
Spreadsheet $spreadsheet)
259 if ($zip->open($pFilename) !==
true) {
260 throw new Exception(
"Could not open {$pFilename} for reading! Error opening file.");
265 $xml = @simplexml_load_string(
266 $this->securityScanner->scan($zip->getFromName(
'meta.xml')),
270 if (
$xml ===
false) {
271 throw new Exception(
'Unable to read data from {$pFilename}');
274 $namespacesMeta =
$xml->getNamespaces(
true);
282 $this->securityScanner->scan($zip->getFromName(
'styles.xml')),
292 $this->securityScanner->scan($zip->getFromName(
'content.xml')),
296 $officeNs = $dom->lookupNamespaceUri(
'office');
297 $tableNs = $dom->lookupNamespaceUri(
'table');
298 $textNs = $dom->lookupNamespaceUri(
'text');
299 $xlinkNs = $dom->lookupNamespaceUri(
'xlink');
301 $pageSettings->readStyleCrossReferences($dom);
303 $autoFilterReader =
new AutoFilter($spreadsheet, $tableNs);
304 $definedNameReader =
new DefinedNames($spreadsheet, $tableNs);
307 $spreadsheets = $dom->getElementsByTagNameNS($officeNs,
'body')
309 ->getElementsByTagNameNS($officeNs,
'spreadsheet');
311 foreach ($spreadsheets as $workbookData) {
313 $tables = $workbookData->getElementsByTagNameNS($tableNs,
'table');
316 foreach ($tables as $worksheetDataSet) {
318 $worksheetName = $worksheetDataSet->getAttributeNS($tableNs,
'name');
322 isset($this->loadSheetsOnly)
324 && !in_array($worksheetName, $this->loadSheetsOnly)
329 $worksheetStyleName = $worksheetDataSet->getAttributeNS($tableNs,
'style-name');
332 if ($worksheetID > 0) {
337 if ($worksheetName) {
341 $spreadsheet->
getActiveSheet()->setTitle((
string) $worksheetName,
false,
false);
346 foreach ($worksheetDataSet->childNodes as $childNode) {
350 if ($childNode->namespaceURI != $tableNs) {
354 $key = $childNode->nodeName;
357 if (strpos(
$key,
':') !==
false) {
358 $keyChunks = explode(
':',
$key);
359 $key = array_pop($keyChunks);
363 case 'table-header-rows':
374 if ($childNode->hasAttributeNS($tableNs,
'number-rows-repeated')) {
375 $rowRepeats = $childNode->getAttributeNS($tableNs,
'number-rows-repeated');
382 foreach ($childNode->childNodes as $cellData) {
384 if (!$this->
getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
392 $formatting = $hyperlink = null;
393 $hasCalculatedValue =
false;
394 $cellDataFormula =
'';
396 if ($cellData->hasAttributeNS($tableNs,
'formula')) {
397 $cellDataFormula = $cellData->getAttributeNS($tableNs,
'formula');
398 $hasCalculatedValue =
true;
402 $annotation = $cellData->getElementsByTagNameNS($officeNs,
'annotation');
404 if ($annotation->length > 0) {
405 $textNode = $annotation->item(0)->getElementsByTagNameNS($textNs,
'p');
407 if ($textNode->length > 0) {
408 $text = $this->scanElementForText($textNode->item(0));
411 ->getComment($columnID . $rowID)
422 foreach ($cellData->childNodes as $item) {
426 if ($item->nodeName ==
'text:p') {
427 $paragraphs[] = $item;
431 if (count($paragraphs) > 0) {
440 foreach ($paragraphs as $pData) {
441 $dataArray[] = $this->scanElementForText($pData);
443 $allCellDataText = implode(
"\n", $dataArray);
445 $type = $cellData->getAttributeNS($officeNs,
'value-type');
450 $dataValue = $allCellDataText;
452 foreach ($paragraphs as $paragraph) {
453 $link = $paragraph->getElementsByTagNameNS($textNs,
'a');
454 if ($link->length > 0) {
455 $hyperlink = $link->item(0)->getAttributeNS($xlinkNs,
'href');
462 $dataValue = ($allCellDataText ==
'TRUE') ?
true :
false;
467 $dataValue = (float) $cellData->getAttributeNS($officeNs,
'value');
478 $dataValue = (float) $cellData->getAttributeNS($officeNs,
'value');
480 if (floor($dataValue) == $dataValue) {
481 $dataValue = (int) $dataValue;
488 $dataValue = (float) $cellData->getAttributeNS($officeNs,
'value');
490 if (floor($dataValue) == $dataValue) {
491 if ($dataValue == (
int) $dataValue) {
492 $dataValue = (int) $dataValue;
499 $value = $cellData->getAttributeNS($officeNs,
'date-value');
502 [$year, $month, $day, $hour, $minute, $second] = explode(
504 $dateObj->format(
'Y m d H i s')
516 if ($dataValue != floor($dataValue)) {
528 $timeValue = $cellData->getAttributeNS($officeNs,
'time-value');
532 '01-01-1970 ' . implode(
':', sscanf($timeValue,
'PT%dH%dM%dS'))
546 if ($hasCalculatedValue) {
548 $cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula,
':=') + 1);
552 if ($cellData->hasAttributeNS($tableNs,
'number-columns-repeated')) {
553 $colRepeats = (int) $cellData->getAttributeNS($tableNs,
'number-columns-repeated');
558 if (
$type !== null) {
559 for (
$i = 0;
$i < $colRepeats; ++
$i) {
565 for ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) {
566 $rID = $rowID + $rowAdjust;
569 ->getCell($columnID . $rID);
572 if ($hasCalculatedValue) {
573 $cell->setValueExplicit($cellDataFormula,
$type);
575 $cell->setValueExplicit($dataValue,
$type);
578 if ($hasCalculatedValue) {
579 $cell->setCalculatedValue($dataValue);
583 if ($formatting !== null) {
585 ->getStyle($columnID . $rID)
587 ->setFormatCode($formatting);
590 ->getStyle($columnID . $rID)
595 if ($hyperlink !== null) {
596 $cell->getHyperlink()
597 ->setUrl($hyperlink);
606 $cellData->hasAttributeNS($tableNs,
'number-columns-spanned')
607 || $cellData->hasAttributeNS($tableNs,
'number-rows-spanned')
610 $columnTo = $columnID;
612 if ($cellData->hasAttributeNS($tableNs,
'number-columns-spanned')) {
614 $columnIndex += (int) $cellData->getAttributeNS($tableNs,
'number-columns-spanned');
622 if ($cellData->hasAttributeNS($tableNs,
'number-rows-spanned')) {
623 $rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs,
'number-rows-spanned') - 1;
626 $cellRange = $columnID . $rowID .
':' . $columnTo . $rowTo;
633 $rowID += $rowRepeats;
638 $pageSettings->setPrintSettingsForWorksheet($spreadsheet->
getActiveSheet(), $worksheetStyleName);
642 $autoFilterReader->read($workbookData);
643 $definedNameReader->read($workbookData);
647 if ($zip->locateName(
'settings.xml') !==
false) {
658 $this->securityScanner->scan($zip->getFromName(
'settings.xml')),
662 $configNs = $dom->lookupNamespaceUri(
'config');
664 $officeNs = $dom->lookupNamespaceUri(
'office');
665 $settings = $dom->getElementsByTagNameNS($officeNs,
'settings')
667 $this->lookForActiveSheet($settings, $spreadsheet, $configNs);
668 $this->lookForSelectedCells($settings, $spreadsheet, $configNs);
671 private function lookForActiveSheet(
DOMElement $settings,
Spreadsheet $spreadsheet,
string $configNs):
void 674 foreach ($settings->getElementsByTagNameNS($configNs,
'config-item') as
$t) {
675 if (
$t->getAttributeNs($configNs,
'name') ===
'ActiveTable') {
687 private function lookForSelectedCells(
DOMElement $settings,
Spreadsheet $spreadsheet,
string $configNs):
void 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;
700 if ($attrName ===
'CursorPositionY') {
701 $setRow = $configItem->nodeValue;
704 $this->
setSelected($spreadsheet, $wsname, $setCol, $setRow);
714 if (is_numeric($setCol) && is_numeric($setRow)) {
716 $spreadsheet->
getSheetByName($wsname)->setSelectedCellByColumnAndRow($setCol + 1, $setRow + 1);
728 protected function scanElementForText(
DOMNode $element)
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') {
740 $cAttr = $child->attributes->getNamedItem(
'c');
742 $multiplier = (int) $cAttr->nodeValue;
747 $str .= str_repeat(
' ', $multiplier);
750 if ($child->hasChildNodes()) {
751 $str .= $this->scanElementForText($child);
766 $value->createText($is);
773 $temp = explode(
'"', $openOfficeFormula);
775 foreach ($temp as &$value) {
777 if ($tKey = !$tKey) {
779 $value = preg_replace(
'/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu',
'$1!$2:$3', $value);
781 $value = preg_replace(
'/\[\$?([^\.]+)\.([^\.]+)\]/miu',
'$1!$2', $value ??
'');
783 $value = preg_replace(
'/\[\.([^\.]+):\.([^\.]+)\]/miu',
'$1:$2', $value ??
'');
785 $value = preg_replace(
'/\[\.([^\.]+)\]/miu',
'$1', $value ??
'');
787 $value = str_replace(
'$$',
'', $value ??
'');
794 $excelFormula = implode(
'"', $temp);
796 return $excelFormula;
getSheetByName($pName)
Get sheet by name.
getActiveSheet()
Get active sheet.
listWorksheetNames($pFilename)
Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object...
setActiveSheetIndexByName($pValue)
Set active sheet index by name.
__construct()
Create a new Ods Reader instance.
convertToExcelFormulaValue(string $openOfficeFormula)
static formattedPHPToExcel($year, $month, $day, $hours=0, $minutes=0, $seconds=0)
formattedPHPToExcel.
listWorksheetInfo($pFilename)
Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns)...
static PHPToExcel($dateValue)
Convert a date from PHP to an MS Excel serialized date/time value.
getReadFilter()
Read filter.
createSheet($sheetIndex=null)
Create sheet and add it to this workbook.
setActiveSheetIndex($pIndex)
Set active sheet index.
processSettings(ZipArchive $zip, Spreadsheet $spreadsheet)
static getInstance(Reader\IReader $reader)
canRead($pFilename)
Can the current IReader read the file?
setSelected(Spreadsheet $spreadsheet, string $wsname, string $setCol, string $setRow)
load($pFilename)
Loads PhpSpreadsheet from file.
static getLibXmlLoaderOptions()
Get default options for libxml loader.
static translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces)
static assertFile($filename)
Assert that given path is an existing file and is readable, otherwise throw exception.
static columnIndexFromString($pString)
Column index from string.
static stringFromColumnIndex($columnIndex)
String from column index.