diff --git a/.gitignore b/.gitignore index dea03b5e1..f76f37bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ analysis *.project /.settings /.idea +/unitTests/phpunit diff --git a/Classes/PHPExcel/Reader/OOCalc.php b/Classes/PHPExcel/Reader/OOCalc.php index a889d9570..5251df6f4 100644 --- a/Classes/PHPExcel/Reader/OOCalc.php +++ b/Classes/PHPExcel/Reader/OOCalc.php @@ -34,34 +34,31 @@ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL * @version ##VERSION##, ##DATE## */ -class PHPExcel_Reader_OOCalc extends PHPExcel_Reader_Abstract implements PHPExcel_Reader_IReader -{ +class PHPExcel_Reader_OOCalc extends PHPExcel_Reader_Abstract implements PHPExcel_Reader_IReader { /** * Formats * * @var array */ - private $styles = array(); + private $styles = []; /** * Create a new PHPExcel_Reader_OOCalc */ - public function __construct() - { - $this->readFilter = new PHPExcel_Reader_DefaultReadFilter(); + public function __construct(){ + $this->readFilter = new PHPExcel_Reader_DefaultReadFilter(); } /** * Can the current PHPExcel_Reader_IReader read the file? * - * @param string $pFilename + * @param string $pFilename * @return boolean * @throws PHPExcel_Reader_Exception */ - public function canRead($pFilename) - { + public function canRead($pFilename){ // Check if file exists - if (!file_exists($pFilename)) { + if(!file_exists($pFilename)){ throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist."); } @@ -75,20 +72,21 @@ public function canRead($pFilename) $mimeType = 'UNKNOWN'; // Load file $zip = new $zipClass; - if ($zip->open($pFilename) === true) { + if($zip->open($pFilename) === true){ // check if it is an OOXML archive $stat = $zip->statName('mimetype'); - if ($stat && ($stat['size'] <= 255)) { + if($stat && ($stat['size'] <= 255)){ $mimeType = $zip->getFromName($stat['name']); - } elseif ($stat = $zip->statName('META-INF/manifest.xml')) { + } + elseif($stat = $zip->statName('META-INF/manifest.xml')){ $xml = simplexml_load_string($this->securityScan($zip->getFromName('META-INF/manifest.xml')), 'SimpleXMLElement', PHPExcel_Settings::getLibXmlLoaderOptions()); $namespacesContent = $xml->getNamespaces(true); - if (isset($namespacesContent['manifest'])) { + if(isset($namespacesContent['manifest'])){ $manifest = $xml->children($namespacesContent['manifest']); - foreach ($manifest as $manifestDataSet) { + foreach($manifest as $manifestDataSet){ $manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']); - if ($manifestAttributes->{'full-path'} == '/') { - $mimeType = (string) $manifestAttributes->{'media-type'}; + if($manifestAttributes->{'full-path'} == '/'){ + $mimeType = (string)$manifestAttributes->{'media-type'}; break; } } @@ -107,48 +105,48 @@ public function canRead($pFilename) /** * Reads names of the worksheets from a file, without parsing the whole file to a PHPExcel object * - * @param string $pFilename + * @param string $pFilename * @throws PHPExcel_Reader_Exception */ - public function listWorksheetNames($pFilename) - { + public function listWorksheetNames($pFilename){ // Check if file exists - if (!file_exists($pFilename)) { + if(!file_exists($pFilename)){ throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist."); } $zipClass = PHPExcel_Settings::getZipClass(); $zip = new $zipClass; - if (!$zip->open($pFilename)) { + if(!$zip->open($pFilename)){ throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! Error opening file."); } - $worksheetNames = array(); + $worksheetNames = []; $xml = new XMLReader(); - $res = $xml->xml($this->securityScanFile('zip://'.realpath($pFilename).'#content.xml'), null, PHPExcel_Settings::getLibXmlLoaderOptions()); + $res = $xml->xml($this->securityScanFile('zip://' . realpath($pFilename) . '#content.xml'), null, PHPExcel_Settings::getLibXmlLoaderOptions()); $xml->setParserProperty(2, true); // Step into the first level of content of the XML $xml->read(); - while ($xml->read()) { + while($xml->read()){ // Quickly jump through to the office:body node - while ($xml->name !== 'office:body') { - if ($xml->isEmptyElement) { + while($xml->name !== 'office:body'){ + if($xml->isEmptyElement){ $xml->read(); - } else { + } + else{ $xml->next(); } } // Now read each node until we find our first table:table node - while ($xml->read()) { - if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) { + while($xml->read()){ + if($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT){ // Loop through each table:table node reading the table:name attribute for each worksheet name - do { + do{ $worksheetNames[] = $xml->getAttribute('table:name'); $xml->next(); - } while ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT); + }while($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT); } } } @@ -159,58 +157,58 @@ public function listWorksheetNames($pFilename) /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns) * - * @param string $pFilename + * @param string $pFilename * @throws PHPExcel_Reader_Exception */ - public function listWorksheetInfo($pFilename) - { + public function listWorksheetInfo($pFilename){ // Check if file exists - if (!file_exists($pFilename)) { + if(!file_exists($pFilename)){ throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist."); } - $worksheetInfo = array(); + $worksheetInfo = []; $zipClass = PHPExcel_Settings::getZipClass(); $zip = new $zipClass; - if (!$zip->open($pFilename)) { + if(!$zip->open($pFilename)){ throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! Error opening file."); } $xml = new XMLReader(); - $res = $xml->xml($this->securityScanFile('zip://'.realpath($pFilename).'#content.xml'), null, PHPExcel_Settings::getLibXmlLoaderOptions()); + $res = $xml->xml($this->securityScanFile('zip://' . realpath($pFilename) . '#content.xml'), null, PHPExcel_Settings::getLibXmlLoaderOptions()); $xml->setParserProperty(2, true); // Step into the first level of content of the XML $xml->read(); - while ($xml->read()) { + while($xml->read()){ // Quickly jump through to the office:body node - while ($xml->name !== 'office:body') { - if ($xml->isEmptyElement) { + while($xml->name !== 'office:body'){ + if($xml->isEmptyElement){ $xml->read(); - } else { + } + else{ $xml->next(); } } - // Now read each node until we find our first table:table node - while ($xml->read()) { - if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) { + // Now read each node until we find our first table:table node + while($xml->read()){ + if($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT){ $worksheetNames[] = $xml->getAttribute('table:name'); - $tmpInfo = array( + $tmpInfo = [ 'worksheetName' => $xml->getAttribute('table:name'), 'lastColumnLetter' => 'A', 'lastColumnIndex' => 0, 'totalRows' => 0, 'totalColumns' => 0, - ); + ]; // Loop through each child node of the table:table element reading $currCells = 0; - do { + do{ $xml->read(); - if ($xml->name == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) { + if($xml->name == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT){ $rowspan = $xml->getAttribute('table:number-rows-repeated'); $rowspan = empty($rowspan) ? 1 : $rowspan; $tmpInfo['totalRows'] += $rowspan; @@ -218,22 +216,24 @@ public function listWorksheetInfo($pFilename) $currCells = 0; // Step into the row $xml->read(); - do { - if ($xml->name == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) { - if (!$xml->isEmptyElement) { + do{ + if($xml->name == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT){ + if(!$xml->isEmptyElement){ $currCells++; $xml->next(); - } else { + } + else{ $xml->read(); } - } elseif ($xml->name == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) { + } + elseif($xml->name == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT){ $mergeSize = $xml->getAttribute('table:number-columns-repeated'); $currCells += $mergeSize; $xml->read(); } - } while ($xml->name != 'table:table-row'); + }while($xml->name != 'table:table-row'); } - } while ($xml->name != 'table:table'); + }while($xml->name != 'table:table'); $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1; @@ -284,12 +284,11 @@ public function listWorksheetInfo($pFilename) /** * Loads PHPExcel from file * - * @param string $pFilename + * @param string $pFilename * @return PHPExcel * @throws PHPExcel_Reader_Exception */ - public function load($pFilename) - { + public function load($pFilename){ // Create new PHPExcel $objPHPExcel = new PHPExcel(); @@ -297,29 +296,31 @@ public function load($pFilename) return $this->loadIntoExisting($pFilename, $objPHPExcel); } - private static function identifyFixedStyleValue($styleList, &$styleAttributeValue) - { + private static function identifyFixedStyleValue($styleList, &$styleAttributeValue){ $styleAttributeValue = strtolower($styleAttributeValue); - foreach ($styleList as $style) { - if ($styleAttributeValue == strtolower($style)) { + foreach($styleList as $style){ + if($styleAttributeValue == strtolower($style)){ $styleAttributeValue = $style; + return true; } } + return false; } /** * Loads PHPExcel from file into PHPExcel instance * - * @param string $pFilename - * @param PHPExcel $objPHPExcel + * @param string $pFilename + * @param PHPExcel $objPHPExcel * @return PHPExcel * @throws PHPExcel_Reader_Exception */ public function loadIntoExisting($pFilename, PHPExcel $objPHPExcel) { - // Check if file exists + +// Check if file exists if (!file_exists($pFilename)) { throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist."); } @@ -329,6 +330,7 @@ public function loadIntoExisting($pFilename, PHPExcel $objPHPExcel) $zipClass = PHPExcel_Settings::getZipClass(); + /** @var \ZipArchive $zip */ $zip = new $zipClass; if (!$zip->open($pFilename)) { throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! Error opening file."); @@ -429,9 +431,13 @@ public function loadIntoExisting($pFilename, PHPExcel $objPHPExcel) $workbook = $xml->children($namespacesContent['office']); foreach ($workbook->body->spreadsheet as $workbookData) { + /** @var \SimpleXMLElement $workbookData */ + $workbookData = $workbookData->children($namespacesContent['table']); $worksheetID = 0; foreach ($workbookData->table as $worksheetDataSet) { + /** @var \SimpleXMLElement $worksheetDataSet */ + $worksheetData = $worksheetDataSet->children($namespacesContent['table']); // print_r($worksheetData); // echo '
'; @@ -457,6 +463,8 @@ public function loadIntoExisting($pFilename, PHPExcel $objPHPExcel) $rowID = 1; foreach ($worksheetData as $key => $rowData) { + /** @var \SimpleXMLElement $rowData */ + // echo ''.$key.'
'; switch ($key) { case 'table-header-rows': @@ -469,6 +477,8 @@ public function loadIntoExisting($pFilename, PHPExcel $objPHPExcel) $rowRepeats = (isset($rowDataTableAttributes['number-rows-repeated'])) ? $rowDataTableAttributes['number-rows-repeated'] : 1; $columnID = 'A'; foreach ($rowData as $key => $cellData) { + /** @var \SimpleXMLElement $cellData */ + if ($this->getReadFilter() !== null) { if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) { continue; @@ -524,16 +534,33 @@ public function loadIntoExisting($pFilename, PHPExcel $objPHPExcel) // Also, here we assume there is no text data is span fields are specified, since // we have no way of knowing proper positioning anyway. foreach ($cellDataText->p as $pData) { - if (isset($pData->span)) { - // span sections do not newline, so we just create one large string here - $spanSection = ""; - foreach ($pData->span as $spanData) { - $spanSection .= $spanData; - } - array_push($dataArray, $spanSection); - } else { - array_push($dataArray, $pData); - } + /** @var \SimpleXMLElement $pData */ + +// if (isset($pData->span)) { +// // span sections do not newline, so we just create one large string here +// $spanSection = ""; +// foreach ($pData->span as $spanData) { +// /** @var \SimpleXMLElement $spanData */ +// +// $spanSection .= $spanData; +// } +// array_push($dataArray, $spanSection); +// } else { + + // SimpleXML sucks, need to use DOMDocument instead + libxml_use_internal_errors(true); + + $doc = new \DOMDocument("1.0"); + $doc->loadXML($pData->saveXML()); + + $str = $this->scanElement($doc->childNodes->item(0)); + + unset($doc); + + libxml_use_internal_errors(false); + + array_push($dataArray, $str); +// } } $allCellDataText = implode($dataArray, "\n"); @@ -685,6 +712,46 @@ public function loadIntoExisting($pFilename, PHPExcel $objPHPExcel) return $objPHPExcel; } + /** + * Recursively scan element + * + * @param DOMNode $element + * @return string + */ + protected function scanElement(DOMNode $element){ + + $str = ""; + foreach($element->childNodes as $child){ + /** @var \DOMNode $child */ + + if($child->nodeType == XML_TEXT_NODE){ + $str .= $child->nodeValue; + } + elseif($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == "text:s"){ + // It's a space + + // Multiple spaces? + if(isset($child->attributes["text:c"])){ + + /** @var \DOMAttr $cAttr */ + $cAttr = $child->attributes["text:c"]; + $multiplier = (int)$cAttr->nodeValue; + } + else{ + $multiplier = 1; + } + + $str .= str_repeat(" ", $multiplier); + } + + if($child->hasChildNodes()){ + $str .= $this->scanElement($child); + } + } + + return $str; + } + private function parseRichText($is = '') { $value = new PHPExcel_RichText(); diff --git a/Classes/PHPExcel/Writer/OpenDocument/Content.php b/Classes/PHPExcel/Writer/OpenDocument/Content.php index a34b1670c..3f8384df6 100644 --- a/Classes/PHPExcel/Writer/OpenDocument/Content.php +++ b/Classes/PHPExcel/Writer/OpenDocument/Content.php @@ -33,11 +33,13 @@ * @package PHPExcel_Writer_OpenDocument * @copyright Copyright (c) 2006 - 2015 PHPExcel (http://www.codeplex.com/PHPExcel) * @author Alexander Pervakov + * @author Paolo Agostinetto */ class PHPExcel_Writer_OpenDocument_Content extends PHPExcel_Writer_OpenDocument_WriterPart { const NUMBER_COLS_REPEATED_MAX = 1024; const NUMBER_ROWS_REPEATED_MAX = 1048576; + const CELL_STYLE_PREFIX = 'ce'; /** * Write content.xml to XML format @@ -100,7 +102,11 @@ public function write(PHPExcel $pPHPExcel = null) $objWriter->writeElement('office:scripts'); $objWriter->writeElement('office:font-face-decls'); - $objWriter->writeElement('office:automatic-styles'); + + // Styles XF + $objWriter->startElement('office:automatic-styles'); + $this->writeXfStyles($objWriter, $pPHPExcel); + $objWriter->endElement(); $objWriter->startElement('office:body'); $objWriter->startElement('office:spreadsheet'); @@ -186,12 +192,20 @@ private function writeCells(PHPExcel_Shared_XMLWriter $objWriter, PHPExcel_Works $prev_column = -1; $cells = $row->getCellIterator(); while ($cells->valid()) { + + /** @var PHPExcel_Cell $cell */ $cell = $cells->current(); $column = PHPExcel_Cell::columnIndexFromString($cell->getColumn()) - 1; $this->writeCellSpan($objWriter, $column, $prev_column); $objWriter->startElement('table:table-cell'); + // Style XF + $style = $cell->getXfIndex(); + if($style !== null){ + $objWriter->writeAttribute('table:style-name', self::CELL_STYLE_PREFIX.$style); + } + switch ($cell->getDataType()) { case PHPExcel_Cell_DataType::TYPE_BOOL: $objWriter->writeAttribute('office:value-type', 'boolean'); @@ -269,4 +283,92 @@ private function writeCellSpan(PHPExcel_Shared_XMLWriter $objWriter, $curColumn, $objWriter->endElement(); } } + + /** + * Write XF cell styles + * + * @param PHPExcel_Shared_XMLWriter $objWriter + * @param PHPExcel $pPHPExcel + * @throws PHPExcel_Exception + */ + private function writeXfStyles(PHPExcel_Shared_XMLWriter $objWriter, PHPExcel $pPHPExcel) + { + foreach($pPHPExcel->getCellXfCollection() as $style) { + + $objWriter->startElement('style:style'); + $objWriter->writeAttribute('style:name', self::CELL_STYLE_PREFIX .$style->getIndex()); + $objWriter->writeAttribute('style:family', 'table-cell'); + $objWriter->writeAttribute('style:parent-style-name', 'Default'); + + /* + * style:text-properties + */ + + // Font + $objWriter->startElement('style:text-properties'); + + $font = $style->getFont(); + + if($font->getBold()) { + $objWriter->writeAttribute('fo:font-weight', 'bold'); + $objWriter->writeAttribute('style:font-weight-complex', 'bold'); + $objWriter->writeAttribute('style:font-weight-asian', 'bold'); + } + + if($font->getItalic()) { + $objWriter->writeAttribute('fo:font-style', 'italic'); + } + + if($color = $font->getColor()) { + $objWriter->writeAttribute('fo:color', sprintf('#%s', $color->getRGB())); + } + + if($size = $font->getSize()) { + $objWriter->writeAttribute('fo:font-size', sprintf('%.1fpt', $size)); + } + + if($font->getUnderline() == \PHPExcel_Style_Font::UNDERLINE_SINGLE) { + $objWriter->writeAttribute('style:text-underline-style', 'solid'); + $objWriter->writeAttribute('style:text-underline-width', 'auto'); + $objWriter->writeAttribute('style:text-underline-color', 'font-color'); + } + + $objWriter->endElement(); // Close style:text-properties + + /* + * style:table-cell-properties + */ + + $objWriter->startElement('style:table-cell-properties'); + $objWriter->writeAttribute('style:rotation-align', 'none'); + + // Fill + if($fill = $style->getFill()) { + switch($fill->getFillType()) { + + case \PHPExcel_Style_Fill::FILL_SOLID: + $objWriter->writeAttribute('fo:background-color', sprintf('#%s', + strtolower($fill->getStartColor()->getRGB()) + )); + break; + + case \PHPExcel_Style_Fill::FILL_GRADIENT_LINEAR: + case \PHPExcel_Style_Fill::FILL_GRADIENT_PATH: + /// TODO :: To be implemented + break; + + case \PHPExcel_Style_Fill::FILL_NONE: + default: + } + } + + $objWriter->endElement(); // Close style:table-cell-properties + + /* + * End + */ + + $objWriter->endElement(); // Close style:style + } + } } diff --git a/unitTests/Classes/PHPExcel/Reader/OOCalcTest.php b/unitTests/Classes/PHPExcel/Reader/OOCalcTest.php new file mode 100644 index 000000000..16f0df089 --- /dev/null +++ b/unitTests/Classes/PHPExcel/Reader/OOCalcTest.php @@ -0,0 +1,26 @@ +load(__DIR__."/../../../rawTestData/Reader/OOCalc/spaces-everywhere.ods"); + + $arr = $file->getActiveSheet()->toArray(); + + $this->assertEquals([ + ["This has 4 spaces before and 2 after "], + ["This only one after "], + ["Test with DIFFERENT styles and multiple spaces: "], + ["test with new \nLines"], + ], $arr); + } +} diff --git a/unitTests/rawTestData/Reader/OOCalc/spaces-everywhere.ods b/unitTests/rawTestData/Reader/OOCalc/spaces-everywhere.ods new file mode 100644 index 000000000..2c0867252 Binary files /dev/null and b/unitTests/rawTestData/Reader/OOCalc/spaces-everywhere.ods differ