diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 748f3ac3..08c61255 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -18,7 +18,7 @@ build: tools: external_code_coverage: - timeout: 3600 + timeout: 600 build_failure_conditions: - 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed diff --git a/CHANGELOG.md b/CHANGELOG.md index 27d72197..22fd8c3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Fix RATE, PRICE, XIRR, and XNPV Functions [#1456](https://github.com/PHPOffice/PhpSpreadsheet/pull/1456) - Save Excel 2010+ functions properly in XLSX [#1461](https://github.com/PHPOffice/PhpSpreadsheet/pull/1461) - Several improvements in HTML writer [#1464](https://github.com/PHPOffice/PhpSpreadsheet/pull/1464) +- Fix incorrect behaviour when saving XLSX file with drawings [#1462](https://github.com/PHPOffice/PhpSpreadsheet/pull/1462), +- Fix Crash while trying setting a cell the value "123456\n" [#1476](https://github.com/PHPOffice/PhpSpreadsheet/pull/1481) ### Changed diff --git a/samples/Basic/07_Reader.php b/samples/Basic/07_Reader.php index 4d9bd79e..67b3ae56 100644 --- a/samples/Basic/07_Reader.php +++ b/samples/Basic/07_Reader.php @@ -17,3 +17,4 @@ $helper->logRead('Xlsx', $filename, $callStartTime); // Save $helper->write($spreadsheet, __FILE__); +unlink($filename); diff --git a/samples/Basic/16_Csv.php b/samples/Basic/16_Csv.php index de753d56..15bbf0d4 100644 --- a/samples/Basic/16_Csv.php +++ b/samples/Basic/16_Csv.php @@ -1,13 +1,15 @@ log('Write to CSV format'); /** @var \PhpOffice\PhpSpreadsheet\Writer\Csv $writer */ -$writer = IOFactory::createWriter($spreadsheet, 'Csv')->setDelimiter(',') +$writer = new CsvWriter($spreadsheet); +$writer->setDelimiter(',') ->setEnclosure('"') ->setSheetIndex(0); @@ -19,13 +21,15 @@ $helper->logWrite($writer, $filename, $callStartTime); $helper->log('Read from CSV format'); /** @var \PhpOffice\PhpSpreadsheet\Reader\Csv $reader */ -$reader = IOFactory::createReader('Csv')->setDelimiter(',') +$reader = new CsvReader(); +$reader->setDelimiter(',') ->setEnclosure('"') ->setSheetIndex(0); $callStartTime = microtime(true); $spreadsheetFromCSV = $reader->load($filename); $helper->logRead('Csv', $filename, $callStartTime); +unlink($filename); // Write Xlsx $helper->write($spreadsheetFromCSV, __FILE__, ['Xlsx']); @@ -33,7 +37,7 @@ $helper->write($spreadsheetFromCSV, __FILE__, ['Xlsx']); // Write CSV $filenameCSV = $helper->getFilename(__FILE__, 'csv'); /** @var \PhpOffice\PhpSpreadsheet\Writer\Csv $writerCSV */ -$writerCSV = IOFactory::createWriter($spreadsheetFromCSV, 'Csv'); +$writerCSV = new CsvWriter($spreadsheetFromCSV); $writerCSV->setExcelCompatibility(true); $callStartTime = microtime(true); diff --git a/samples/Basic/20_Read_Xls.php b/samples/Basic/20_Read_Xls.php index 9e5fa014..daeaf664 100644 --- a/samples/Basic/20_Read_Xls.php +++ b/samples/Basic/20_Read_Xls.php @@ -17,6 +17,7 @@ $helper->logWrite($writer, $filename, $callStartTime); $callStartTime = microtime(true); $spreadsheet = IOFactory::load($filename); $helper->logRead('Xls', $filename, $callStartTime); +unlink($filename); // Save $helper->write($spreadsheet, __FILE__); diff --git a/samples/Basic/24_Readfilter.php b/samples/Basic/24_Readfilter.php index 844996f2..ab1c2e41 100644 --- a/samples/Basic/24_Readfilter.php +++ b/samples/Basic/24_Readfilter.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet; use PhpOffice\PhpSpreadsheet\Reader\IReadFilter; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; use PhpOffice\PhpSpreadsheet\Writer\Xlsx; require __DIR__ . '/../Header.php'; @@ -29,10 +30,11 @@ class MyReadFilter implements IReadFilter } $helper->log('Load from Xlsx file'); -$reader = IOFactory::createReader('Xlsx'); +$reader = new XlsxReader(); $reader->setReadFilter(new MyReadFilter()); $callStartTime = microtime(true); $spreadsheet = $reader->load($filename); +unlink($filename); $helper->logRead('Xlsx', $filename, $callStartTime); $helper->log('Remove unnecessary rows'); $spreadsheet->getActiveSheet()->removeRow(2, 18); diff --git a/samples/Basic/28_Iterator.php b/samples/Basic/28_Iterator.php index 4aec7a92..104dc47f 100644 --- a/samples/Basic/28_Iterator.php +++ b/samples/Basic/28_Iterator.php @@ -1,21 +1,22 @@ getTemporaryFilename(); -$writer = new Xlsx($sampleSpreadsheet); +$writer = new XlsxWriter($sampleSpreadsheet); $callStartTime = microtime(true); $writer->save($filename); $helper->logWrite($writer, $filename, $callStartTime); $callStartTime = microtime(true); -$reader = IOFactory::createReader('Xlsx'); +$reader = new XlsxReader(); $spreadsheet = $reader->load($filename); $helper->logRead('Xlsx', $filename, $callStartTime); +unlink($filename); $helper->log('Iterate worksheets'); foreach ($spreadsheet->getWorksheetIterator() as $worksheet) { $helper->log('Worksheet - ' . $worksheet->getTitle()); diff --git a/samples/Basic/44_Worksheet_info.php b/samples/Basic/44_Worksheet_info.php index 33c0cd05..57822369 100644 --- a/samples/Basic/44_Worksheet_info.php +++ b/samples/Basic/44_Worksheet_info.php @@ -24,3 +24,5 @@ var_dump($sheetList); $helper->log('Worksheet Names:'); var_dump($sheetInfo); + +unlink($filename); diff --git a/samples/Chart/34_Chart_update.php b/samples/Chart/34_Chart_update.php index a4287927..5d725c49 100644 --- a/samples/Chart/34_Chart_update.php +++ b/samples/Chart/34_Chart_update.php @@ -1,20 +1,22 @@ getTemporaryFilename(); -$writer = new Xlsx($sampleSpreadsheet); +$writer = new XlsxWriter($sampleSpreadsheet); +$writer->setIncludeCharts(true); $writer->save($filename); $helper->log('Load from Xlsx file'); -$reader = IOFactory::createReader('Xlsx'); +$reader = new XlsxReader(); $reader->setIncludeCharts(true); $spreadsheet = $reader->load($filename); +unlink($filename); $helper->log('Update cell data values that are displayed in the chart'); $worksheet = $spreadsheet->getActiveSheet(); @@ -31,7 +33,7 @@ $worksheet->fromArray( // Save Excel 2007 file $filename = $helper->getFilename(__FILE__); -$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer = new XlsxWriter($spreadsheet); $writer->setIncludeCharts(true); $callStartTime = microtime(true); $writer->save($filename); diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 0be9ab6f..cdfe7b53 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -37,6 +37,10 @@ class Calculation const RETURN_ARRAY_AS_VALUE = 'value'; const RETURN_ARRAY_AS_ARRAY = 'array'; + const FORMULA_OPEN_FUNCTION_BRACE = '{'; + const FORMULA_CLOSE_FUNCTION_BRACE = '}'; + const FORMULA_STRING_QUOTE = '"'; + private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE; /** @@ -2593,11 +2597,11 @@ class Calculation for ($i = 0; $i < $strlen; ++$i) { $chr = mb_substr($formula, $i, 1); switch ($chr) { - case '{': + case self::FORMULA_OPEN_FUNCTION_BRACE: $inBraces = true; break; - case '}': + case self::FORMULA_CLOSE_FUNCTION_BRACE: $inBraces = false; break; @@ -2626,10 +2630,10 @@ class Calculation if (self::$localeLanguage !== 'en_us') { $inBraces = false; // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators - if (strpos($formula, '"') !== false) { + if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) { // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded // the formula - $temp = explode('"', $formula); + $temp = explode(self::FORMULA_STRING_QUOTE, $formula); $i = false; foreach ($temp as &$value) { // Only count/replace in alternating array entries @@ -2640,7 +2644,7 @@ class Calculation } unset($value); // Then rebuild the formula string - $formula = implode('"', $temp); + $formula = implode(self::FORMULA_STRING_QUOTE, $temp); } else { // If there's no quoted strings, then we do a simple count/replace $formula = preg_replace($from, $to, $formula); @@ -2741,7 +2745,7 @@ class Calculation return $value; } // Return strings wrapped in quotes - return '"' . $value . '"'; + return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE; // Convert numeric errors to NaN error } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) { return Functions::NAN(); @@ -2760,7 +2764,7 @@ class Calculation public static function unwrapResult($value) { if (is_string($value)) { - if ((isset($value[0])) && ($value[0] == '"') && (substr($value, -1) == '"')) { + if ((isset($value[0])) && ($value[0] == self::FORMULA_STRING_QUOTE) && (substr($value, -1) == self::FORMULA_STRING_QUOTE)) { return substr($value, 1, -1); } // Convert numeric errors to NAN error @@ -3227,8 +3231,8 @@ class Calculation } return '{ ' . implode($rpad, $returnMatrix) . ' }'; - } elseif (is_string($value) && (trim($value, '"') == $value)) { - return '"' . $value . '"'; + } elseif (is_string($value) && (trim($value, self::FORMULA_STRING_QUOTE) == $value)) { + return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE; } elseif (is_bool($value)) { return ($value) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE']; } @@ -3282,34 +3286,34 @@ class Calculation */ private function convertMatrixReferences($formula) { - static $matrixReplaceFrom = ['{', ';', '}']; + static $matrixReplaceFrom = [self::FORMULA_OPEN_FUNCTION_BRACE, ';', self::FORMULA_CLOSE_FUNCTION_BRACE]; static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))']; // Convert any Excel matrix references to the MKMATRIX() function - if (strpos($formula, '{') !== false) { + if (strpos($formula, self::FORMULA_OPEN_FUNCTION_BRACE) !== false) { // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators - if (strpos($formula, '"') !== false) { + if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) { // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded // the formula - $temp = explode('"', $formula); + $temp = explode(self::FORMULA_STRING_QUOTE, $formula); // Open and Closed counts used for trapping mismatched braces in the formula $openCount = $closeCount = 0; $i = false; foreach ($temp as &$value) { // Only count/replace in alternating array entries if ($i = !$i) { - $openCount += substr_count($value, '{'); - $closeCount += substr_count($value, '}'); + $openCount += substr_count($value, self::FORMULA_OPEN_FUNCTION_BRACE); + $closeCount += substr_count($value, self::FORMULA_CLOSE_FUNCTION_BRACE); $value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value); } } unset($value); // Then rebuild the formula string - $formula = implode('"', $temp); + $formula = implode(self::FORMULA_STRING_QUOTE, $temp); } else { // If there's no quoted strings, then we do a simple count/replace - $openCount = substr_count($formula, '{'); - $closeCount = substr_count($formula, '}'); + $openCount = substr_count($formula, self::FORMULA_OPEN_FUNCTION_BRACE); + $closeCount = substr_count($formula, self::FORMULA_CLOSE_FUNCTION_BRACE); $formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula); } // Trap for mismatched braces and trigger an appropriate error @@ -3715,9 +3719,9 @@ class Calculation } $localeConstant = false; - if ($opCharacter == '"') { + if ($opCharacter == self::FORMULA_STRING_QUOTE) { // UnEscape any quotes within the string - $val = self::wrapResult(str_replace('""', '"', self::unwrapResult($val))); + $val = self::wrapResult(str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($val))); } elseif (is_numeric($val)) { if ((strpos($val, '.') !== false) || (stripos($val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) { $val = (float) $val; @@ -4058,7 +4062,7 @@ class Calculation $result = '#VALUE!'; } } else { - $result = '"' . str_replace('""', '"', self::unwrapResult($operand1) . self::unwrapResult($operand2)) . '"'; + $result = self::FORMULA_STRING_QUOTE . str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)) . self::FORMULA_STRING_QUOTE; } $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result)); $stack->push('Value', $result); @@ -4078,9 +4082,15 @@ class Calculation $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]); } } - $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow); - $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect)); - $stack->push('Value', $cellIntersect, $cellRef); + if (count(Functions::flattenArray($cellIntersect)) === 0) { + $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect)); + $stack->push('Error', Functions::null(), null); + } else { + $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . + Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow); + $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect)); + $stack->push('Value', $cellIntersect, $cellRef); + } break; } @@ -4284,7 +4294,7 @@ class Calculation $branchStore[$storeKey] = self::$excelConstants[$excelConstant]; } $this->debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->showTypeDetails(self::$excelConstants[$excelConstant])); - } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == '"') || ($token[0] == '#')) { + } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == self::FORMULA_STRING_QUOTE) || ($token[0] == '#')) { $stack->push('Value', $token); if (isset($storeKey)) { $branchStore[$storeKey] = $token; @@ -4329,7 +4339,7 @@ class Calculation if (is_string($operand)) { // We only need special validations for the operand if it is a string // Start by stripping off the quotation marks we use to identify true excel string values internally - if ($operand > '' && $operand[0] == '"') { + if ($operand > '' && $operand[0] == self::FORMULA_STRING_QUOTE) { $operand = self::unwrapResult($operand); } // If the string is a numeric value, we treat it as a numeric, so no further testing @@ -4342,7 +4352,7 @@ class Calculation return false; } elseif (!Shared\StringHelper::convertToNumberIfFraction($operand)) { // If not a numeric or a fraction, then it's a text string, and so can't be used in mathematical binary operations - $stack->push('Value', '#VALUE!'); + $stack->push('Error', '#VALUE!'); $this->debugLog->writeDebugLog('Evaluation Result is a ', $this->showTypeDetails('#VALUE!')); return false; @@ -4402,10 +4412,10 @@ class Calculation } // Simple validate the two operands if they are string values - if (is_string($operand1) && $operand1 > '' && $operand1[0] == '"') { + if (is_string($operand1) && $operand1 > '' && $operand1[0] == self::FORMULA_STRING_QUOTE) { $operand1 = self::unwrapResult($operand1); } - if (is_string($operand2) && $operand2 > '' && $operand2[0] == '"') { + if (is_string($operand2) && $operand2 > '' && $operand2[0] == self::FORMULA_STRING_QUOTE) { $operand2 = self::unwrapResult($operand2); } @@ -4570,7 +4580,7 @@ class Calculation case '/': if ($operand2 == 0) { // Trap for Divide by Zero error - $stack->push('Value', '#DIV/0!'); + $stack->push('Error', '#DIV/0!'); $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails('#DIV/0!')); return false; diff --git a/src/PhpSpreadsheet/Calculation/MathTrig.php b/src/PhpSpreadsheet/Calculation/MathTrig.php index d92ba404..85f94cc2 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig.php @@ -1339,6 +1339,8 @@ class MathTrig // Is it a numeric value? if ((is_numeric($arg)) && (!is_string($arg))) { $returnValue += $arg; + } elseif (Functions::isError($arg)) { + return $arg; } } diff --git a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php index 3a676c4f..693446e6 100644 --- a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php +++ b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php @@ -65,6 +65,8 @@ class DefaultValueBinder implements IValueBinder return DataType::TYPE_STRING; } elseif ((strpos($pValue, '.') === false) && ($pValue > PHP_INT_MAX)) { return DataType::TYPE_STRING; + } elseif (!is_numeric($pValue)) { + return DataType::TYPE_STRING; } return DataType::TYPE_NUMERIC; diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index 5e12a2fb..2fe85b6f 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -933,7 +933,12 @@ class Html extends BaseReader */ private function setBorderStyle(Style $cellStyle, $styleValue, $type): void { - [, $borderStyle, $color] = explode(' ', $styleValue); + if (trim($styleValue) === Border::BORDER_NONE) { + $borderStyle = Border::BORDER_NONE; + $color = null; + } else { + [, $borderStyle, $color] = explode(' ', $styleValue); + } $cellStyle->applyFromArray([ 'borders' => [ diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 77035a39..797e59ea 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1000,12 +1000,13 @@ class Xlsx extends BaseReader Settings::getLibXmlLoaderOptions() ); $drawings = []; - foreach ($relsVML->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') { - $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']); + if (isset($relsVML->Relationship)) { + foreach ($relsVML->Relationship as $ele) { + if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') { + $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']); + } } } - // Fetch VML document $vmlDrawing = simplexml_load_string( $this->securityScanner->scan($this->getFromZipArchive($zip, $vmlRelationship)), diff --git a/src/PhpSpreadsheet/Shared/CodePage.php b/src/PhpSpreadsheet/Shared/CodePage.php index b395293c..97cbfbbe 100644 --- a/src/PhpSpreadsheet/Shared/CodePage.php +++ b/src/PhpSpreadsheet/Shared/CodePage.php @@ -6,6 +6,65 @@ use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; class CodePage { + private static $pageArray = [ + 0 => 'CP1252', // CodePage is not always correctly set when the xls file was saved by Apple's Numbers program + 367 => 'ASCII', // ASCII + 437 => 'CP437', // OEM US + //720 => 'notsupported', // OEM Arabic + 737 => 'CP737', // OEM Greek + 775 => 'CP775', // OEM Baltic + 850 => 'CP850', // OEM Latin I + 852 => 'CP852', // OEM Latin II (Central European) + 855 => 'CP855', // OEM Cyrillic + 857 => 'CP857', // OEM Turkish + 858 => 'CP858', // OEM Multilingual Latin I with Euro + 860 => 'CP860', // OEM Portugese + 861 => 'CP861', // OEM Icelandic + 862 => 'CP862', // OEM Hebrew + 863 => 'CP863', // OEM Canadian (French) + 864 => 'CP864', // OEM Arabic + 865 => 'CP865', // OEM Nordic + 866 => 'CP866', // OEM Cyrillic (Russian) + 869 => 'CP869', // OEM Greek (Modern) + 874 => 'CP874', // ANSI Thai + 932 => 'CP932', // ANSI Japanese Shift-JIS + 936 => 'CP936', // ANSI Chinese Simplified GBK + 949 => 'CP949', // ANSI Korean (Wansung) + 950 => 'CP950', // ANSI Chinese Traditional BIG5 + 1200 => 'UTF-16LE', // UTF-16 (BIFF8) + 1250 => 'CP1250', // ANSI Latin II (Central European) + 1251 => 'CP1251', // ANSI Cyrillic + 1252 => 'CP1252', // ANSI Latin I (BIFF4-BIFF7) + 1253 => 'CP1253', // ANSI Greek + 1254 => 'CP1254', // ANSI Turkish + 1255 => 'CP1255', // ANSI Hebrew + 1256 => 'CP1256', // ANSI Arabic + 1257 => 'CP1257', // ANSI Baltic + 1258 => 'CP1258', // ANSI Vietnamese + 1361 => 'CP1361', // ANSI Korean (Johab) + 10000 => 'MAC', // Apple Roman + 10001 => 'CP932', // Macintosh Japanese + 10002 => 'CP950', // Macintosh Chinese Traditional + 10003 => 'CP1361', // Macintosh Korean + 10004 => 'MACARABIC', // Apple Arabic + 10005 => 'MACHEBREW', // Apple Hebrew + 10006 => 'MACGREEK', // Macintosh Greek + 10007 => 'MACCYRILLIC', // Macintosh Cyrillic + 10008 => 'CP936', // Macintosh - Simplified Chinese (GB 2312) + 10010 => 'MACROMANIA', // Macintosh Romania + 10017 => 'MACUKRAINE', // Macintosh Ukraine + 10021 => 'MACTHAI', // Macintosh Thai + 10029 => 'MACCENTRALEUROPE', // Macintosh Central Europe + 10079 => 'MACICELAND', // Macintosh Icelandic + 10081 => 'MACTURKISH', // Macintosh Turkish + 10082 => 'MACCROATIAN', // Macintosh Croatian + 21010 => 'UTF-16LE', // UTF-16 (BIFF8) This isn't correct, but some Excel writer libraries erroneously use Codepage 21010 for UTF-16LE + 32768 => 'MAC', // Apple Roman + //32769 => 'unsupported', // ANSI Latin I (BIFF2-BIFF3) + 65000 => 'UTF-7', // Unicode (UTF-7) + 65001 => 'UTF-8', // Unicode (UTF-8) + ]; + /** * Convert Microsoft Code Page Identifier to Code Page Name which iconv * and mbstring understands. @@ -14,123 +73,20 @@ class CodePage * * @return string Code Page Name */ - public static function numberToName($codePage) + public static function numberToName(int $codePage): string { - switch ($codePage) { - case 367: - return 'ASCII'; // ASCII - case 437: - return 'CP437'; // OEM US - case 720: - throw new PhpSpreadsheetException('Code page 720 not supported.'); // OEM Arabic - case 737: - return 'CP737'; // OEM Greek - case 775: - return 'CP775'; // OEM Baltic - case 850: - return 'CP850'; // OEM Latin I - case 852: - return 'CP852'; // OEM Latin II (Central European) - case 855: - return 'CP855'; // OEM Cyrillic - case 857: - return 'CP857'; // OEM Turkish - case 858: - return 'CP858'; // OEM Multilingual Latin I with Euro - case 860: - return 'CP860'; // OEM Portugese - case 861: - return 'CP861'; // OEM Icelandic - case 862: - return 'CP862'; // OEM Hebrew - case 863: - return 'CP863'; // OEM Canadian (French) - case 864: - return 'CP864'; // OEM Arabic - case 865: - return 'CP865'; // OEM Nordic - case 866: - return 'CP866'; // OEM Cyrillic (Russian) - case 869: - return 'CP869'; // OEM Greek (Modern) - case 874: - return 'CP874'; // ANSI Thai - case 932: - return 'CP932'; // ANSI Japanese Shift-JIS - case 936: - return 'CP936'; // ANSI Chinese Simplified GBK - case 949: - return 'CP949'; // ANSI Korean (Wansung) - case 950: - return 'CP950'; // ANSI Chinese Traditional BIG5 - case 1200: - return 'UTF-16LE'; // UTF-16 (BIFF8) - case 1250: - return 'CP1250'; // ANSI Latin II (Central European) - case 1251: - return 'CP1251'; // ANSI Cyrillic - case 0: - // CodePage is not always correctly set when the xls file was saved by Apple's Numbers program - case 1252: - return 'CP1252'; // ANSI Latin I (BIFF4-BIFF7) - case 1253: - return 'CP1253'; // ANSI Greek - case 1254: - return 'CP1254'; // ANSI Turkish - case 1255: - return 'CP1255'; // ANSI Hebrew - case 1256: - return 'CP1256'; // ANSI Arabic - case 1257: - return 'CP1257'; // ANSI Baltic - case 1258: - return 'CP1258'; // ANSI Vietnamese - case 1361: - return 'CP1361'; // ANSI Korean (Johab) - case 10000: - return 'MAC'; // Apple Roman - case 10001: - return 'CP932'; // Macintosh Japanese - case 10002: - return 'CP950'; // Macintosh Chinese Traditional - case 10003: - return 'CP1361'; // Macintosh Korean - case 10004: - return 'MACARABIC'; // Apple Arabic - case 10005: - return 'MACHEBREW'; // Apple Hebrew - case 10006: - return 'MACGREEK'; // Macintosh Greek - case 10007: - return 'MACCYRILLIC'; // Macintosh Cyrillic - case 10008: - return 'CP936'; // Macintosh - Simplified Chinese (GB 2312) - case 10010: - return 'MACROMANIA'; // Macintosh Romania - case 10017: - return 'MACUKRAINE'; // Macintosh Ukraine - case 10021: - return 'MACTHAI'; // Macintosh Thai - case 10029: - return 'MACCENTRALEUROPE'; // Macintosh Central Europe - case 10079: - return 'MACICELAND'; // Macintosh Icelandic - case 10081: - return 'MACTURKISH'; // Macintosh Turkish - case 10082: - return 'MACCROATIAN'; // Macintosh Croatian - case 21010: - return 'UTF-16LE'; // UTF-16 (BIFF8) This isn't correct, but some Excel writer libraries erroneously use Codepage 21010 for UTF-16LE - case 32768: - return 'MAC'; // Apple Roman - case 32769: - throw new PhpSpreadsheetException('Code page 32769 not supported.'); // ANSI Latin I (BIFF2-BIFF3) - case 65000: - return 'UTF-7'; // Unicode (UTF-7) - case 65001: - return 'UTF-8'; // Unicode (UTF-8) + if (array_key_exists($codePage, self::$pageArray)) { + return self::$pageArray[$codePage]; + } + if ($codePage == 720 || $codePage == 32769) { + throw new PhpSpreadsheetException("Code page $codePage not supported."); // OEM Arabic } throw new PhpSpreadsheetException('Unknown codepage: ' . $codePage); } + + public static function getEncodings(): array + { + return self::$pageArray; + } } diff --git a/src/PhpSpreadsheet/Shared/Date.php b/src/PhpSpreadsheet/Shared/Date.php index fd49c1ec..9dc99292 100644 --- a/src/PhpSpreadsheet/Shared/Date.php +++ b/src/PhpSpreadsheet/Shared/Date.php @@ -4,10 +4,10 @@ namespace PhpOffice\PhpSpreadsheet\Shared; use DateTimeInterface; use DateTimeZone; -use Exception; use PhpOffice\PhpSpreadsheet\Calculation\DateTime; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Cell\Cell; +use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; class Date @@ -97,17 +97,18 @@ class Date * @param DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions * * @return bool Success or failure - * @return bool Success or failure */ public static function setDefaultTimezone($timeZone) { - if ($timeZone = self::validateTimeZone($timeZone)) { + try { + $timeZone = self::validateTimeZone($timeZone); self::$defaultTimeZone = $timeZone; - - return true; + $retval = true; + } catch (PhpSpreadsheetException $e) { + $retval = false; } - return false; + return $retval; } /** @@ -130,17 +131,17 @@ class Date * @param DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object * * @return DateTimeZone The timezone as a timezone object - * @return DateTimeZone The timezone as a timezone object */ - protected static function validateTimeZone($timeZone) + private static function validateTimeZone($timeZone) { - if (is_object($timeZone) && $timeZone instanceof DateTimeZone) { + if ($timeZone instanceof DateTimeZone) { return $timeZone; - } elseif (is_string($timeZone)) { + } + if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) { return new DateTimeZone($timeZone); } - throw new Exception('Invalid timezone'); + throw new PhpSpreadsheetException('Invalid timezone'); } /** @@ -316,7 +317,7 @@ class Date */ public static function isDateTime(Cell $pCell) { - return is_numeric($pCell->getValue()) && + return is_numeric($pCell->getCalculatedValue()) && self::isDateTimeFormat( $pCell->getWorksheet()->getStyle( $pCell->getCoordinate() diff --git a/src/PhpSpreadsheet/Shared/TimeZone.php b/src/PhpSpreadsheet/Shared/TimeZone.php index a87987df..43fd3653 100644 --- a/src/PhpSpreadsheet/Shared/TimeZone.php +++ b/src/PhpSpreadsheet/Shared/TimeZone.php @@ -23,7 +23,7 @@ class TimeZone */ private static function validateTimeZone($timezone) { - return in_array($timezone, DateTimeZone::listIdentifiers()); + return in_array($timezone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC)); } /** @@ -73,10 +73,6 @@ class TimeZone $timezone = self::$timezone; } - if ($timezone == 'UST') { - return 0; - } - $objTimezone = new DateTimeZone($timezone); $transitions = $objTimezone->getTransitions($timestamp, $timestamp); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php index c2876065..a4e5769e 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php @@ -183,7 +183,6 @@ class Rels extends WriterPart $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); // Write drawing relationships? - $d = 0; $drawingOriginalIds = []; $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData(); if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'])) { @@ -197,13 +196,19 @@ class Rels extends WriterPart } if (($pWorksheet->getDrawingCollection()->count() > 0) || (count($charts) > 0) || $drawingOriginalIds) { - $relPath = '../drawings/drawing' . $pWorksheetId . '.xml'; - $rId = ++$d; + $rId = 1; + // Use original $relPath to get original $rId. + // Take first. In future can be overwritten. + // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::writeDrawings) + reset($drawingOriginalIds); + $relPath = key($drawingOriginalIds); if (isset($drawingOriginalIds[$relPath])) { $rId = (int) (substr($drawingOriginalIds[$relPath], 3)); } + // Generate new $relPath to write drawing relationship + $relPath = '../drawings/drawing' . $pWorksheetId . '.xml'; $this->writeRelationship( $objWriter, $rId, diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 15359a4c..3d47eeaa 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -1215,6 +1215,7 @@ class Worksheet extends WriterPart if (isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'])) { $drawingOriginalIds = $unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds']; // take first. In future can be overriten + // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels::writeWorksheetRelationships) $rId = reset($drawingOriginalIds); } diff --git a/tests/PhpSpreadsheetTests/Calculation/DefinedNameConfusedForCellTest.php b/tests/PhpSpreadsheetTests/Calculation/DefinedNameConfusedForCellTest.php index 12d73356..76886c23 100644 --- a/tests/PhpSpreadsheetTests/Calculation/DefinedNameConfusedForCellTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/DefinedNameConfusedForCellTest.php @@ -18,5 +18,6 @@ class DefinedNameConfusedForCellTest extends TestCase $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); $writer->save($filename); self::assertTrue(true); + unlink($filename); } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php new file mode 100644 index 00000000..84cac747 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php @@ -0,0 +1,54 @@ +spreadSheet = new Spreadsheet(); + $this->spreadSheet->getActiveSheet() + ->setCellValue('A1', 1) + ->setCellValue('B1', 2) + ->setCellValue('C1', 3) + ->setCellValue('A2', 4) + ->setCellValue('B2', 5) + ->setCellValue('C2', 6) + ->setCellValue('A3', 7) + ->setCellValue('B3', 8) + ->setCellValue('C3', 9); + } + + /** + * @dataProvider providerRangeEvaluation + * + * @param mixed $formula + * @param int $expectedResult + */ + public function testRangeEvaluation($formula, $expectedResult): void + { + $workSheet = $this->spreadSheet->getActiveSheet(); + $workSheet->setCellValue('E1', $formula); + + $actualRresult = $workSheet->getCell('E1')->getCalculatedValue(); + self::assertSame($expectedResult, $actualRresult); + } + + public function providerRangeEvaluation() + { + return[ + ['=SUM(A1:B3,A1:C2)', 48], + ['=SUM(A1:B3 A1:C2)', 12], + ['=SUM(A1:A3,C1:C3)', 30], + ['=SUM(A1:A3 C1:C3)', Functions::null()], + ['=SUM(A1:B2,B2:C3)', 40], + ['=SUM(A1:B2 B2:C3)', 5], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php b/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php index 90dabce3..e13cd942 100644 --- a/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php +++ b/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php @@ -57,6 +57,7 @@ class DefaultValueBinderTest extends TestCase ['#REF!'], [new DateTime()], [new DateTimeImmutable()], + ['123456\n'], ]; } diff --git a/tests/PhpSpreadsheetTests/Reader/CsvTest.php b/tests/PhpSpreadsheetTests/Reader/CsvTest.php index cb2b6196..e4ccd931 100644 --- a/tests/PhpSpreadsheetTests/Reader/CsvTest.php +++ b/tests/PhpSpreadsheetTests/Reader/CsvTest.php @@ -200,6 +200,37 @@ EOF; self::assertEquals($expected, $sheet->getCell('B3')->getValue()); } + public function testLineBreakEscape(): void + { + $reader = new Csv(); + $spreadsheet = $reader->load('tests/data/Reader/CSV/line_break_in_enclosure_with_escaped_quotes.csv'); + $sheet = $spreadsheet->getActiveSheet(); + $expected = <<getCell('B3')->getValue()); + } + + public function testUtf32LineBreakEscape(): void + { + $reader = new Csv(); + $reader->setInputEncoding('UTF-32LE'); + $spreadsheet = $reader->load('tests/data/Reader/CSV/line_break_escaped_32le.csv'); + $sheet = $spreadsheet->getActiveSheet(); + $expected = <<getCell('B3')->getValue()); + } + public function testSeparatorLine(): void { $reader = new Csv(); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx2Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx2Test.php index 4d0b6a8a..1220c378 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx2Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx2Test.php @@ -11,14 +11,6 @@ use PHPUnit\Framework\TestCase; class Xlsx2Test extends TestCase { - protected function tearDown(): void - { - $outfile = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); - if (file_exists($outfile)) { - unlink($outfile); - } - } - public function testLoadXlsxConditionalFormatting2(): void { // Make sure Conditionals are read correctly from existing file @@ -63,6 +55,7 @@ class Xlsx2Test extends TestCase $writer = IOFactory::createWriter($spreadshee1, 'Xlsx'); $writer->save($outfile); $spreadsheet = $reader->load($outfile); + unlink($outfile); $worksheet = $spreadsheet->getActiveSheet(); $conditionalStyle = $worksheet->getConditionalStyles('A2:A8'); @@ -110,6 +103,7 @@ class Xlsx2Test extends TestCase $writer->save($outfile); $reader = IOFactory::createReader('Xlsx'); $spreadsheet = $reader->load($outfile); + unlink($outfile); $worksheet = $spreadsheet->getActiveSheet(); $conditionalStyle = $worksheet->getConditionalStyles('A1:A6'); diff --git a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php index 9e0b5f66..b326c142 100644 --- a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php @@ -222,6 +222,7 @@ class XlsxTest extends TestCase $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($excel); $writer->save($resultFilename); $excel = $reader->load($resultFilename); + unlink($resultFilename); // Fake assert. The only thing we need is to ensure the file is loaded without exception self::assertNotNull($excel); } diff --git a/tests/PhpSpreadsheetTests/Shared/CodePageTest.php b/tests/PhpSpreadsheetTests/Shared/CodePageTest.php index b86f9015..2bdbda72 100644 --- a/tests/PhpSpreadsheetTests/Shared/CodePageTest.php +++ b/tests/PhpSpreadsheetTests/Shared/CodePageTest.php @@ -24,6 +24,22 @@ class CodePageTest extends TestCase return require 'tests/data/Shared/CodePage.php'; } + public function testCoverage(): void + { + $covered = []; + $expected = CodePage::getEncodings(); + foreach ($expected as $key => $val) { + $covered[$key] = 0; + } + $tests = $this->providerCodePage(); + foreach ($tests as $test) { + $covered[$test[1]] = 1; + } + foreach ($covered as $key => $val) { + self::assertEquals(1, $val, "Codepage $key not tested"); + } + } + public function testNumberToNameWithInvalidCodePage(): void { $invalidCodePage = 12345; diff --git a/tests/PhpSpreadsheetTests/Shared/DateTest.php b/tests/PhpSpreadsheetTests/Shared/DateTest.php index 23b31076..7254635e 100644 --- a/tests/PhpSpreadsheetTests/Shared/DateTest.php +++ b/tests/PhpSpreadsheetTests/Shared/DateTest.php @@ -3,10 +3,23 @@ namespace PhpOffice\PhpSpreadsheetTests\Shared; use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PHPUnit\Framework\TestCase; class DateTest extends TestCase { + private $dttimezone; + + protected function setUp(): void + { + $this->dttimezone = Date::getDefaultTimeZone(); + } + + protected function tearDown(): void + { + Date::setDefaultTimeZone($this->dttimezone); + } + public function testSetExcelCalendar(): void { $calendarValues = [ @@ -168,4 +181,41 @@ class DateTest extends TestCase { return require 'tests/data/Shared/Date/ExcelToTimestamp1900Timezone.php'; } + + public function testVarious(): void + { + Date::setDefaultTimeZone('UTC'); + self::assertFalse(Date::stringToExcel('2019-02-29')); + self::assertTrue((bool) Date::stringToExcel('2019-02-28')); + self::assertTrue((bool) Date::stringToExcel('2019-02-28 11:18')); + self::assertFalse(Date::stringToExcel('2019-02-28 11:71')); + $date = Date::PHPToExcel('2020-01-01'); + self::assertEquals(43831.0, $date); + $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('B1', 'x'); + $val = $sheet->getCell('B1')->getValue(); + self::assertFalse(Date::timestampToExcel($val)); + $cell = $sheet->getCell('A1'); + self::assertNotNull($cell); + $cell->setValue($date); + $sheet->getStyle('A1') + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME); + self::assertTrue(null !== $cell && Date::isDateTime($cell)); + $cella2 = $sheet->getCell('A2'); + self::assertNotNull($cella2); + $cella2->setValue('=A1+2'); + $sheet->getStyle('A2') + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME); + self::assertTrue(null !== $cella2 && Date::isDateTime($cella2)); + $cella3 = $sheet->getCell('A3'); + self::assertNotNull($cella3); + $cella3->setValue('=A1+4'); + $sheet->getStyle('A3') + ->getNumberFormat() + ->setFormatCode('0.00E+00'); + self::assertFalse(null !== $cella3 && Date::isDateTime($cella3)); + } } diff --git a/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php b/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php index f6e2f5d5..ff38badf 100644 --- a/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php +++ b/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php @@ -2,11 +2,29 @@ namespace PhpOffice\PhpSpreadsheetTests\Shared; +use DateTime; +use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\TimeZone; use PHPUnit\Framework\TestCase; class TimeZoneTest extends TestCase { + private $tztimezone; + + private $dttimezone; + + protected function setUp(): void + { + $this->tztimezone = TimeZone::getTimeZone(); + $this->dttimezone = Date::getDefaultTimeZone(); + } + + protected function tearDown(): void + { + TimeZone::setTimeZone($this->tztimezone); + Date::setDefaultTimeZone($this->dttimezone); + } + public function testSetTimezone(): void { $timezoneValues = [ @@ -20,13 +38,51 @@ class TimeZoneTest extends TestCase foreach ($timezoneValues as $timezoneValue) { $result = TimeZone::setTimezone($timezoneValue); self::assertTrue($result); + $result = Date::setDefaultTimezone($timezoneValue); + self::assertTrue($result); } } + public function testSetTimezoneBackwardCompatible(): void + { + $bcTimezone = 'Etc/GMT+10'; + $result = TimeZone::setTimezone($bcTimezone); + self::assertTrue($result); + $result = Date::setDefaultTimezone($bcTimezone); + self::assertTrue($result); + } + public function testSetTimezoneWithInvalidValue(): void { - $unsupportedTimezone = 'Etc/GMT+10'; + $unsupportedTimezone = 'XEtc/GMT+10'; $result = TimeZone::setTimezone($unsupportedTimezone); self::assertFalse($result); + $result = Date::setDefaultTimezone($unsupportedTimezone); + self::assertFalse($result); + } + + public function testTimeZoneAdjustmentsInvalidTz(): void + { + $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); + $dtobj = DateTime::createFromFormat('Y-m-d H:i:s', '2008-09-22 00:00:00'); + $tstmp = $dtobj->getTimestamp(); + $unsupportedTimeZone = 'XEtc/GMT+10'; + TimeZone::getTimeZoneAdjustment($unsupportedTimeZone, $tstmp); + } + + public function testTimeZoneAdjustments(): void + { + $dtobj = DateTime::createFromFormat('Y-m-d H:i:s', '2008-01-01 00:00:00'); + $tstmp = $dtobj->getTimestamp(); + $supportedTimeZone = 'UTC'; + $adj = TimeZone::getTimeZoneAdjustment($supportedTimeZone, $tstmp); + self::assertEquals(0, $adj); + $supportedTimeZone = 'America/Toronto'; + $adj = TimeZone::getTimeZoneAdjustment($supportedTimeZone, $tstmp); + self::assertEquals(-18000, $adj); + $supportedTimeZone = 'America/Chicago'; + TimeZone::setTimeZone($supportedTimeZone); + $adj = TimeZone::getTimeZoneAdjustment(null, $tstmp); + self::assertEquals(-21600, $adj); } } diff --git a/tests/PhpSpreadsheetTests/Writer/Html/ImagesRootTest.php b/tests/PhpSpreadsheetTests/Writer/Html/ImagesRootTest.php index 5c887d53..6cc7f18f 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/ImagesRootTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/ImagesRootTest.php @@ -9,6 +9,18 @@ use PhpOffice\PhpSpreadsheetTests\Functional; class ImagesRootTest extends Functional\AbstractFunctional { + private $curdir; + + protected function setUp(): void + { + $this->curdir = getcwd(); + } + + protected function tearDown(): void + { + chdir($this->curdir); + } + public function testImagesRoot(): void { $spreadsheet = new Spreadsheet(); @@ -20,7 +32,6 @@ class ImagesRootTest extends Functional\AbstractFunctional $newdir = __DIR__ . '/../../../data/Reader/HTML'; $stub = 'image.jpg'; $imagePath = "./$stub"; - $curdir = getcwd(); chdir($newdir); self::assertFileExists($imagePath); $drawing->setPath($imagePath); @@ -34,7 +45,6 @@ class ImagesRootTest extends Functional\AbstractFunctional $writer = new Html($spreadsheet); $writer->setImagesRoot($root); $html = $writer->generateHTMLAll(); - chdir($curdir); $dom = new DOMDocument(); $dom->loadHTML($html); $body = $dom->getElementsByTagName('body')[0]; diff --git a/tests/PhpSpreadsheetTests/Writer/Html/InvalidFileNameTest.php b/tests/PhpSpreadsheetTests/Writer/Html/InvalidFileNameTest.php index 7baa2338..36a40e96 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/InvalidFileNameTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/InvalidFileNameTest.php @@ -30,9 +30,8 @@ class InvalidFileNameTest extends Functional\AbstractFunctional $writer->save(''); } - public function testEmptyTempdirNamePdf(): void + public function testNotEmptyTempdirNamePdf(): void { - $this->expectException(WriterException::class); $spreadsheet = new Spreadsheet(); $spreadsheet->getActiveSheet()->getCell('A1')->setValue('Cell 1'); $writer = new Mpdf($spreadsheet); @@ -41,6 +40,16 @@ class InvalidFileNameTest extends Functional\AbstractFunctional $writer->setPaperSize(PageSetup::PAPERSIZE_LEDGER); self::assertEquals($writer->getPaperSize(), PageSetup::PAPERSIZE_LEDGER); self::assertEquals(File::sysGetTempDir() . '/phpsppdf', $writer->getTempDir()); + $writer->setTempDir(File::sysGetTempDir()); + self::assertEquals(File::sysGetTempDir(), $writer->getTempDir()); + } + + public function testEmptyTempdirNamePdf(): void + { + $this->expectException(WriterException::class); + $spreadsheet = new Spreadsheet(); + $spreadsheet->getActiveSheet()->getCell('A1')->setValue('Cell 1'); + $writer = new Mpdf($spreadsheet); $writer->setTempDir(''); } diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/FormulaErrTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/FormulaErrTest.php index 61c70cb6..21a7c928 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xls/FormulaErrTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xls/FormulaErrTest.php @@ -9,14 +9,6 @@ use PHPUnit\Framework\TestCase; class FormulaErrTest extends TestCase { - protected function tearDown(): void - { - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); - if (file_exists($filename)) { - unlink($filename); - } - } - public function testFormulaError(): void { $obj = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); @@ -31,6 +23,7 @@ class FormulaErrTest extends TestCase $writer->save($filename); $reader = IOFactory::createReader('Xls'); $robj = $reader->load($filename); + unlink($filename); $sheet0 = $robj->setActiveSheetIndex(0); $a1 = $sheet0->getCell('A1')->getCalculatedValue(); self::assertEquals(2, $a1); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php new file mode 100644 index 00000000..d6ad77c6 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php @@ -0,0 +1,45 @@ +prevValue = Settings::getLibXmlLoaderOptions(); + + // Disable validating XML with the DTD + Settings::setLibXmlLoaderOptions($this->prevValue & ~LIBXML_DTDVALID & ~LIBXML_DTDATTR & ~LIBXML_DTDLOAD); + } + + protected function tearDown(): void + { + Settings::setLibXmlLoaderOptions($this->prevValue); + } + + /** + * Test save and load XLSX file with drawing on 2nd worksheet. + */ + public function testSaveLoadWithDrawingOn2ndWorksheet(): void + { + // Read spreadsheet from file + $inputFilename = 'tests/data/Writer/XLSX/drawing_on_2nd_page.xlsx'; + $reader = new Xlsx(); + $spreadsheet = $reader->load($inputFilename); + + // Save spreadsheet to file and read it back + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + + // Fake assert. The only thing we need is to ensure the file is loaded without exception + self::assertNotNull($reloadedSpreadsheet); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php index aad074de..746b9846 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php @@ -28,6 +28,7 @@ class FloatsRetainedTest extends TestCase $reader = new Reader(); $sheet = $reader->load($outputFilename); + unlink($outputFilename); self::assertSame($value, $sheet->getActiveSheet()->getCell('A1')->getValue()); } diff --git a/tests/data/Cell/DefaultValueBinder.php b/tests/data/Cell/DefaultValueBinder.php index 53ba6281..1f0c40e0 100644 --- a/tests/data/Cell/DefaultValueBinder.php +++ b/tests/data/Cell/DefaultValueBinder.php @@ -73,4 +73,8 @@ return [ 'e', '#DIV/0!', ], + [ + 's', + '123456\n', + ], ]; diff --git a/tests/data/Reader/CSV/line_break_escaped_32le.csv b/tests/data/Reader/CSV/line_break_escaped_32le.csv new file mode 100644 index 00000000..8e0f0243 Binary files /dev/null and b/tests/data/Reader/CSV/line_break_escaped_32le.csv differ diff --git a/tests/data/Reader/CSV/line_break_in_enclosure_with_escaped_quotes.csv b/tests/data/Reader/CSV/line_break_in_enclosure_with_escaped_quotes.csv index e84db1b5..01ce36a6 100644 --- a/tests/data/Reader/CSV/line_break_in_enclosure_with_escaped_quotes.csv +++ b/tests/data/Reader/CSV/line_break_in_enclosure_with_escaped_quotes.csv @@ -1,21 +1,21 @@ Name,Copy,URL -Test,"This is a \"test csv file\" -with both \"line breaks\" -and \"escaped -quotes\" that breaks +Test,"This is a ""test csv file"" +with both ""line breaks"" +and ""escaped +quotes"" that breaks the delimiters",http://google.com -Test,"This is a \"test csv file\" -with both \"line breaks\" -and \"escaped -quotes\" that breaks +Test,"This is a ""test csv file"" +with both ""line breaks"" +and ""escaped +quotes"" that breaks the delimiters",http://google.com -Test,"This is a \"test csv file\" -with both \"line breaks\" -and \"escaped -quotes\" that breaks +Test,"This is a ""test csv file"" +with both ""line breaks"" +and ""escaped +quotes"" that breaks the delimiters",http://google.com -Test,"This is a \"test csv file\" -with both \"line breaks\" -and \"escaped -quotes\" that breaks +Test,"This is a ""test csv file"" +with both ""line breaks"" +and ""escaped +quotes"" that breaks the delimiters",http://google.com diff --git a/tests/data/Shared/CodePage.php b/tests/data/Shared/CodePage.php index 1cf09d88..82bb23e4 100644 --- a/tests/data/Shared/CodePage.php +++ b/tests/data/Shared/CodePage.php @@ -1,6 +1,11 @@