mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-09-04 06:33:33 +00:00
Merge branch 'master' into CalcEngine_Structured-References
This commit is contained in:
@@ -184,6 +184,22 @@ code:
|
||||
it disables several Office2007 file format options, resulting in a
|
||||
lower-featured Office2007 spreadsheet.
|
||||
|
||||
### Form Control Fields
|
||||
|
||||
PhpSpreadsheet offers limited support for Forms Controls (buttons,
|
||||
checkboxes, etc.). The support is available only for Excel 2007 format,
|
||||
and is offered solely to allow loading a spreadsheet with such controls
|
||||
and saving it as a new file.
|
||||
Support is not available for adding such elements to the spreadsheet,
|
||||
nor even to locate them to determine their properties
|
||||
(so you can't modify or delete them).
|
||||
Modifications to a worksheet with controls are "caveat emptor";
|
||||
some modifications will work correctly,
|
||||
but others are very likely to cause problems,
|
||||
e.g. adding a comment to the worksheet,
|
||||
or inserting or deleting rows or columns in a manner that would
|
||||
cause the controls to change location.
|
||||
|
||||
## Excel 5 (BIFF) file format
|
||||
|
||||
Xls file format is the old Excel file format, implemented in
|
||||
@@ -1099,7 +1115,7 @@ Flags that are available that can be passed to the Reader in this way include:
|
||||
| Slk | N/A | NO | NO |
|
||||
| Csv | N/A | NO | NO |
|
||||
|
||||
Likewise, when saving a file using a Writer, loaded charts wil not be saved unless you explicitly tell the Writer to include them:
|
||||
Likewise, when saving a file using a Writer, loaded charts will not be saved unless you explicitly tell the Writer to include them:
|
||||
|
||||
```php
|
||||
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
||||
require __DIR__ . '/../Header.php';
|
||||
|
||||
$helper->log('Start');
|
||||
|
||||
$inputFileType = 'Xlsx';
|
||||
$inputFileName = __DIR__ . '/sampleData/formscomments.xlsx';
|
||||
|
||||
$helper->log('Loading file ' . $inputFileName . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$helper->log('Loading all WorkSheets');
|
||||
$reader->setLoadAllSheets();
|
||||
$spreadsheet = $reader->load($inputFileName);
|
||||
|
||||
// Save
|
||||
$helper->write($spreadsheet, __FILE__, ['Xlsx']);
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
|
||||
$helper->log('end');
|
||||
@@ -17,5 +17,6 @@ $spreadsheet = $reader->load($inputFileName);
|
||||
|
||||
// Save
|
||||
$helper->write($spreadsheet, __FILE__);
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
|
||||
$helper->log('end');
|
||||
|
||||
Binary file not shown.
@@ -51,8 +51,11 @@ class NonPeriodic
|
||||
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
|
||||
$found = false;
|
||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||
if (!is_numeric($f1) || !is_numeric($f2)) {
|
||||
break;
|
||||
if (!is_numeric($f1)) {
|
||||
return $f1;
|
||||
}
|
||||
if (!is_numeric($f2)) {
|
||||
return $f2;
|
||||
}
|
||||
$f1 = (float) $f1;
|
||||
$f2 = (float) $f2;
|
||||
@@ -68,11 +71,32 @@ class NonPeriodic
|
||||
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
return ExcelError::NAN();
|
||||
if ($found) {
|
||||
return self::xirrPart3($values, $dates, $x1, $x2);
|
||||
}
|
||||
|
||||
return self::xirrPart3($values, $dates, $x1, $x2);
|
||||
// Newton-Raphson didn't work - try bisection
|
||||
$x1 = $guess - 0.5;
|
||||
$x2 = $guess + 0.5;
|
||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||
$f1 = self::xnpvOrdered($x1, $values, $dates, false, true);
|
||||
$f2 = self::xnpvOrdered($x2, $values, $dates, false, true);
|
||||
if (!is_numeric($f1) || !is_numeric($f2)) {
|
||||
break;
|
||||
}
|
||||
if ($f1 * $f2 <= 0) {
|
||||
$found = true;
|
||||
|
||||
break;
|
||||
}
|
||||
$x1 -= 0.5;
|
||||
$x2 += 0.5;
|
||||
}
|
||||
if ($found) {
|
||||
return self::xirrBisection($values, $dates, $x1, $x2);
|
||||
}
|
||||
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,6 +214,45 @@ class NonPeriodic
|
||||
return $rslt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|string
|
||||
*/
|
||||
private static function xirrBisection(array $values, array $dates, float $x1, float $x2)
|
||||
{
|
||||
$rslt = ExcelError::NAN();
|
||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||
$rslt = ExcelError::NAN();
|
||||
$f1 = self::xnpvOrdered($x1, $values, $dates, false, true);
|
||||
$f2 = self::xnpvOrdered($x2, $values, $dates, false, true);
|
||||
if (!is_numeric($f1) || !is_numeric($f2)) {
|
||||
break;
|
||||
}
|
||||
$f1 = (float) $f1;
|
||||
$f2 = (float) $f2;
|
||||
if (abs($f1) < self::FINANCIAL_PRECISION && abs($f2) < self::FINANCIAL_PRECISION) {
|
||||
break;
|
||||
}
|
||||
if ($f1 * $f2 > 0) {
|
||||
break;
|
||||
}
|
||||
$rslt = ($x1 + $x2) / 2;
|
||||
$f3 = self::xnpvOrdered($rslt, $values, $dates, false, true);
|
||||
if (!is_float($f3)) {
|
||||
break;
|
||||
}
|
||||
if ($f3 * $f1 < 0) {
|
||||
$x2 = $rslt;
|
||||
} else {
|
||||
$x1 = $rslt;
|
||||
}
|
||||
if (abs($f3) < self::FINANCIAL_PRECISION) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $rslt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $rate
|
||||
* @param mixed $values
|
||||
@@ -197,7 +260,7 @@ class NonPeriodic
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
private static function xnpvOrdered($rate, $values, $dates, bool $ordered = true)
|
||||
private static function xnpvOrdered($rate, $values, $dates, bool $ordered = true, bool $capAtNegative1 = false)
|
||||
{
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$values = Functions::flattenArray($values);
|
||||
@@ -206,6 +269,9 @@ class NonPeriodic
|
||||
|
||||
try {
|
||||
self::validateXnpv($rate, $values, $dates);
|
||||
if ($capAtNegative1 && $rate <= -1) {
|
||||
$rate = -1.0 + 1.0E-10;
|
||||
}
|
||||
$date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
|
||||
@@ -122,9 +122,12 @@ class Xlsx extends BaseReader
|
||||
return is_array($value) ? $value : [];
|
||||
}
|
||||
|
||||
private function loadZip(string $filename, string $ns = ''): SimpleXMLElement
|
||||
private function loadZip(string $filename, string $ns = '', bool $replaceUnclosedBr = false): SimpleXMLElement
|
||||
{
|
||||
$contents = $this->getFromZipArchive($this->zip, $filename);
|
||||
if ($replaceUnclosedBr) {
|
||||
$contents = str_replace('<br>', '<br/>', $contents);
|
||||
}
|
||||
$rels = simplexml_load_string(
|
||||
$this->securityScanner->scan($contents),
|
||||
'SimpleXMLElement',
|
||||
@@ -1029,6 +1032,7 @@ class Xlsx extends BaseReader
|
||||
|
||||
// later we will remove from it real vmlComments
|
||||
$unparsedVmlDrawings = $vmlComments;
|
||||
$vmlDrawingContents = [];
|
||||
|
||||
// Loop through VML comments
|
||||
foreach ($vmlComments as $relName => $relPath) {
|
||||
@@ -1037,7 +1041,7 @@ class Xlsx extends BaseReader
|
||||
|
||||
try {
|
||||
// no namespace okay - processed with Xpath
|
||||
$vmlCommentsFile = $this->loadZip($relPath, '');
|
||||
$vmlCommentsFile = $this->loadZip($relPath, '', true);
|
||||
$vmlCommentsFile->registerXPathNamespace('v', Namespaces::URN_VML);
|
||||
} catch (Throwable $ex) {
|
||||
//Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData
|
||||
@@ -1047,6 +1051,7 @@ class Xlsx extends BaseReader
|
||||
// Locate VML drawings image relations
|
||||
$drowingImages = [];
|
||||
$VMLDrawingsRelations = dirname($relPath) . '/_rels/' . basename($relPath) . '.rels';
|
||||
$vmlDrawingContents[$relName] = $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath));
|
||||
if ($zip->locateName($VMLDrawingsRelations)) {
|
||||
$relsVMLDrawing = $this->loadZip($VMLDrawingsRelations, Namespaces::RELATIONSHIPS);
|
||||
foreach ($relsVMLDrawing->Relationship as $elex) {
|
||||
@@ -1519,6 +1524,14 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($xmlSheet->legacyDrawing && !$this->readDataOnly) {
|
||||
foreach ($xmlSheet->legacyDrawing as $drawing) {
|
||||
$drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id');
|
||||
if (isset($vmlDrawingContents[$drawingRelId])) {
|
||||
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['legacyDrawing'] = $vmlDrawingContents[$drawingRelId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unparsed drawing AlternateContent
|
||||
$xmlAltDrawing = $this->loadZip((string) $fileDrawing, Namespaces::COMPATIBILITY);
|
||||
|
||||
@@ -455,14 +455,17 @@ class Xlsx extends BaseWriter
|
||||
}
|
||||
|
||||
// Add comment relationship parts
|
||||
if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) {
|
||||
$legacy = $unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['legacyDrawing'] ?? null;
|
||||
if (count($this->spreadSheet->getSheet($i)->getComments()) > 0 || $legacy !== null) {
|
||||
// VML Comments relationships
|
||||
$zipContent['xl/drawings/_rels/vmlDrawing' . ($i + 1) . '.vml.rels'] = $this->getWriterPartRels()->writeVMLDrawingRelationships($this->spreadSheet->getSheet($i));
|
||||
|
||||
// VML Comments
|
||||
$zipContent['xl/drawings/vmlDrawing' . ($i + 1) . '.vml'] = $this->getWriterPartComments()->writeVMLComments($this->spreadSheet->getSheet($i));
|
||||
$zipContent['xl/drawings/vmlDrawing' . ($i + 1) . '.vml'] = $legacy ?? $this->getWriterPartComments()->writeVMLComments($this->spreadSheet->getSheet($i));
|
||||
}
|
||||
|
||||
// Comments
|
||||
// Comments
|
||||
if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) {
|
||||
$zipContent['xl/comments' . ($i + 1) . '.xml'] = $this->getWriterPartComments()->writeComments($this->spreadSheet->getSheet($i));
|
||||
|
||||
// Media
|
||||
@@ -477,7 +480,9 @@ class Xlsx extends BaseWriter
|
||||
// Add unparsed relationship parts
|
||||
if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) {
|
||||
foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) {
|
||||
$zipContent[$vmlDrawing['filePath']] = $vmlDrawing['content'];
|
||||
if (!isset($zipContent[$vmlDrawing['filePath']])) {
|
||||
$zipContent[$vmlDrawing['filePath']] = $vmlDrawing['content'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -239,14 +239,16 @@ class Rels extends WriterPart
|
||||
|
||||
// Write comments relationship?
|
||||
$i = 1;
|
||||
if (count($worksheet->getComments()) > 0) {
|
||||
if (count($worksheet->getComments()) > 0 || isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['legacyDrawing'])) {
|
||||
$this->writeRelationship(
|
||||
$objWriter,
|
||||
'_comments_vml' . $i,
|
||||
Namespaces::VML,
|
||||
'../drawings/vmlDrawing' . $worksheetId . '.vml'
|
||||
);
|
||||
}
|
||||
|
||||
if (count($worksheet->getComments()) > 0) {
|
||||
$this->writeRelationship(
|
||||
$objWriter,
|
||||
'_comments' . $i,
|
||||
|
||||
@@ -1333,7 +1333,8 @@ class Worksheet extends WriterPart
|
||||
private function writeLegacyDrawing(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
|
||||
{
|
||||
// If sheet contains comments, add the relationships
|
||||
if (count($worksheet->getComments()) > 0) {
|
||||
$unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData();
|
||||
if (count($worksheet->getComments()) > 0 || isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['legacyDrawing'])) {
|
||||
$objWriter->startElement('legacyDrawing');
|
||||
$objWriter->writeAttribute('r:id', 'rId_comments_vml1');
|
||||
$objWriter->endElement();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
use PhpOffice\PhpSpreadsheetTests\Custom\ComplexAssert;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
// Sanity tests for functions which have been moved out of Engineering
|
||||
@@ -28,4 +29,60 @@ class MovedFunctionsTest extends TestCase
|
||||
self::assertEquals(357, /** @scrutinizer ignore-deprecated */ Engineering::OCTTODEC(545));
|
||||
self::assertEquals('1AB', /** @scrutinizer ignore-deprecated */ Engineering::OCTTOHEX(653));
|
||||
}
|
||||
|
||||
public function testOthers(): void
|
||||
{
|
||||
self::assertEqualsWithDelta(30596.33413506702, /** @scrutinizer ignore-deprecated */ Engineering::BESSELI(-12.5, 0), 1E-8);
|
||||
self::assertEqualsWithDelta(0.146884054700421, /** @scrutinizer ignore-deprecated */ Engineering::BESSELJ(-12.5, 0), 1E-8);
|
||||
self::assertEqualsWithDelta(2.20786908479938, /** @scrutinizer ignore-deprecated */ Engineering::BESSELK(0.125, 0), 1E-8);
|
||||
self::assertEqualsWithDelta(-1.38968063456627, /** @scrutinizer ignore-deprecated */ Engineering::BESSELY(0.125, 0), 1E-8);
|
||||
self::assertEqualsWithDelta(0.0, /** @scrutinizer ignore-deprecated */ Engineering::DELTA(-0.75, -1.5), 1E-8);
|
||||
self::assertEqualsWithDelta(0.0112834155558496, /** @scrutinizer ignore-deprecated */ Engineering::ERF(0.01), 1E-8);
|
||||
self::assertEqualsWithDelta(0.98871658444415, /** @scrutinizer ignore-deprecated */ Engineering::ERFC(0.01), 1E-8);
|
||||
self::assertEqualsWithDelta(0.0112834155558496, /** @scrutinizer ignore-deprecated */ Engineering::ERFPRECISE(0.01), 1E-8);
|
||||
self::assertEqualsWithDelta(1.0, /** @scrutinizer ignore-deprecated */ Engineering::GESTEP(-0.75, -1.5), 1E-8);
|
||||
}
|
||||
|
||||
public function testConversions(): void
|
||||
{
|
||||
self::assertEqualsWithDelta(1.942559385723E-03, /** @scrutinizer ignore-deprecated */ Engineering::CONVERTUOM(1.0, 'ozm', 'sg'), 1E-8);
|
||||
self::assertContains('Temperature', /** @scrutinizer ignore-deprecated */ Engineering::getConversionGroups());
|
||||
self::assertArrayHasKey('Weight and Mass', /** @scrutinizer ignore-deprecated */ Engineering::getConversionGroupUnits());
|
||||
self::assertEquals('Degrees Celsius', /** @scrutinizer ignore-deprecated */ Engineering::getConversionGroupUnitDetails('Temperature')['Temperature'][0]['description']);
|
||||
self::assertEquals('yotta', /** @scrutinizer ignore-deprecated */ Engineering::getConversionMultipliers()['Y']['name']);
|
||||
self::assertEquals(1024, /** @scrutinizer ignore-deprecated */ Engineering::getBinaryConversionMultipliers()['ki']['multiplier']);
|
||||
}
|
||||
|
||||
public function testImaginary(): void
|
||||
{
|
||||
$complexAssert = new ComplexAssert();
|
||||
$complexAssert->setDelta(1.0E-8);
|
||||
self::assertSame('3+4i', /** @scrutinizer ignore-deprecated */ Engineering::COMPLEX(3, 4));
|
||||
self::assertEqualsWithDelta(5.67, /** @scrutinizer ignore-deprecated */ Engineering::IMAGINARY('12.34+5.67j'), 1E-8);
|
||||
self::assertEqualsWithDelta(12.34, /** @scrutinizer ignore-deprecated */ Engineering::IMREAL('12.34+5.67j'), 1E-8);
|
||||
self::assertEqualsWithDelta(13.58029822942, /** @scrutinizer ignore-deprecated */ Engineering::IMABS('12.34+5.67j'), 1E-8);
|
||||
self::assertEqualsWithDelta(0.43071059555, /** @scrutinizer ignore-deprecated */ Engineering::IMARGUMENT('12.34+5.67j'), 1E-8);
|
||||
$complexAssert->runAssertComplexEquals('12.34-5.67j', /** @scrutinizer ignore-deprecated */ Engineering::IMCONJUGATE('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('141.319179436356+32.547610312508j', /** @scrutinizer ignore-deprecated */ Engineering::IMCOS('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('93502.0563713182121-65794.6618967782119j', /** @scrutinizer ignore-deprecated */ Engineering::IMCOSH('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('93502.0563713182121-65794.6618967782119j', /** @scrutinizer ignore-deprecated */ Engineering::IMCOSH('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('-0.0000104004141424230319-1.00002138037057154j', /** @scrutinizer ignore-deprecated */ Engineering::IMCOT('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('-0.00154774455592154432-0.00671986631601416928j', /** @scrutinizer ignore-deprecated */ Engineering::IMCSC('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('7.15308425027293823E-6+5.03341614148979354E-6j', /** @scrutinizer ignore-deprecated */ Engineering::IMCSCH('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('0.0961415519586104-0.00694248653276682j', /** @scrutinizer ignore-deprecated */ Engineering::IMDIV('12.34+5.67j', '123.45+67.89j'));
|
||||
$complexAssert->runAssertComplexEquals('187004.11273906-131589.323796073j', /** @scrutinizer ignore-deprecated */ Engineering::IMEXP('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('2.60862008281875+0.430710595550204j', /** @scrutinizer ignore-deprecated */ Engineering::IMLN('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('3.76344325733562+0.621384040306436j', /** @scrutinizer ignore-deprecated */ Engineering::IMLOG2('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('1.13290930735019+0.187055234944717j', /** @scrutinizer ignore-deprecated */ Engineering::IMLOG10('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('120.1267+139.9356j', /** @scrutinizer ignore-deprecated */ Engineering::IMPOWER('12.34+5.67j', 2));
|
||||
$complexAssert->runAssertComplexEquals('6454.936089+8718.895647i', /** @scrutinizer ignore-deprecated */ Engineering::IMPRODUCT('12.34+5.67i', '123.45+67.89i', '5.67'));
|
||||
$complexAssert->runAssertComplexEquals('0.00671973874162309199-0.00154764157870523791j', /** @scrutinizer ignore-deprecated */ Engineering::IMSEC('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('7.15308425036177674E-6+5.03341614116724074E-6j', /** @scrutinizer ignore-deprecated */ Engineering::IMSECH('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('-32.5483841590412+141.315819535092j', /** @scrutinizer ignore-deprecated */ Engineering::IMSIN('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('93502.0563677416700-65794.6618992949199j', /** @scrutinizer ignore-deprecated */ Engineering::IMSINH('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('3.60002071031685+0.787495469644252j', /** @scrutinizer ignore-deprecated */ Engineering::IMSQRT('12.34+5.67j'));
|
||||
$complexAssert->runAssertComplexEquals('-111.11-62.22j', /** @scrutinizer ignore-deprecated */ Engineering::IMSUB('12.34+5.67j', '123.45+67.89j'));
|
||||
$complexAssert->runAssertComplexEquals('135.79+73.56j', /** @scrutinizer ignore-deprecated */ Engineering::IMSUM('12.34+5.67j', '123.45+67.89j'));
|
||||
$complexAssert->runAssertComplexEquals('-0.0000103999694261435177+0.999978619978377253j', /** @scrutinizer ignore-deprecated */ Engineering::IMTAN('12.34+5.67j'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\NamedRange;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
@@ -69,29 +68,6 @@ class HLookupTest extends TestCase
|
||||
return require 'tests/data/Calculation/LookupRef/HLOOKUP.php';
|
||||
}
|
||||
|
||||
public function testGrandfathered(): void
|
||||
{
|
||||
// Second parameter is supposed to be array of arrays.
|
||||
// Some old tests called function directly using array of strings;
|
||||
// ensure these work as before.
|
||||
$expectedResult = '#REF!';
|
||||
$result = /** @scrutinizer ignore-deprecated */ LookupRef::HLOOKUP(
|
||||
'Selection column',
|
||||
['Selection column', 'Value to retrieve'],
|
||||
5,
|
||||
false
|
||||
);
|
||||
self::assertSame($expectedResult, $result);
|
||||
$expectedResult = 'Value to retrieve';
|
||||
$result = /** @scrutinizer ignore-deprecated */ LookupRef::HLOOKUP(
|
||||
'Selection column',
|
||||
['Selection column', 'Value to retrieve'],
|
||||
2,
|
||||
false
|
||||
);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerHLookupNamedRange
|
||||
*/
|
||||
|
||||
@@ -19,11 +19,84 @@ class MovedFunctionsTest extends TestCase
|
||||
{
|
||||
public function testMovedFunctions(): void
|
||||
{
|
||||
self::assertSame('$G$3', /** @scrutinizer ignore-deprecated */ LookupRef::cellAddress(3, 7));
|
||||
self::assertSame(3, /** @scrutinizer ignore-deprecated */ LookupRef::COLUMN('C5'));
|
||||
self::assertSame(2, /** @scrutinizer ignore-deprecated */ LookupRef::COLUMNS([[1, 2], [3, 5], [7, 9]]));
|
||||
self::assertSame(30, /** @scrutinizer ignore-deprecated */ LookupRef::CHOOSE(3, 10, 20, 30, 40));
|
||||
self::assertSame(ExcelError::REF(), /** @scrutinizer ignore-deprecated */ LookupRef::FORMULATEXT('A1'));
|
||||
self::assertSame(ExcelError::REF(), /** @scrutinizer ignore-deprecated */ LookupRef::HYPERLINK('https://phpspreadsheet.readthedocs.io/en/latest/', 'Read the Docs'));
|
||||
self::assertSame(60, /** @scrutinizer ignore-deprecated */ LookupRef::INDEX([[10, 20, 30], [40, 50, 60], [70, 80, 90]], 2, 3));
|
||||
self::assertSame(3, /** @scrutinizer ignore-deprecated */ LookupRef::MATCH(3, [1, 10, 3, 8], 0));
|
||||
self::assertSame('#VALUE!', /** @scrutinizer ignore-deprecated */ LookupRef::OFFSET(null));
|
||||
self::assertSame(5, /** @scrutinizer ignore-deprecated */ LookupRef::ROW('C5'));
|
||||
self::assertSame(3, /** @scrutinizer ignore-deprecated */ LookupRef::ROWS([[1, 2], [3, 5], [7, 9]]));
|
||||
self::assertSame([[1, 2], [3, 4]], /** @scrutinizer ignore-deprecated */ LookupRef::TRANSPOSE([[1, 3], [2, 4]]));
|
||||
}
|
||||
|
||||
public function testLookup(): void
|
||||
{
|
||||
$densityGrid = [
|
||||
['Density', 'Viscosity', 'Temperature'],
|
||||
[0.457, 3.55, 500],
|
||||
[0.525, 3.25, 400],
|
||||
[0.616, 2.93, 300],
|
||||
[0.675, 2.75, 250],
|
||||
[0.746, 2.57, 200],
|
||||
[0.835, 2.38, 150],
|
||||
[0.946, 2.17, 100],
|
||||
[1.090, 1.95, 50],
|
||||
[1.290, 1.71, 0],
|
||||
];
|
||||
$expectedResult = 100;
|
||||
$result = /** @scrutinizer ignore-deprecated */ LookupRef::VLOOKUP(1, $densityGrid, 3, true);
|
||||
self::assertSame($expectedResult, $result);
|
||||
$orderGrid = [
|
||||
['Order ID', 10247, 10249, 10250, 10251, 10252, 10253],
|
||||
['Unit Price', 14.00, 18.60, 7.70, 16.80, 16.80, 64.80],
|
||||
['Quantity', 12, 9, 10, 6, 20, 40],
|
||||
];
|
||||
$expectedResult = 16.80;
|
||||
$result = /** @scrutinizer ignore-deprecated */ LookupRef::HLOOKUP(10251, $orderGrid, 2, false);
|
||||
self::assertSame($expectedResult, $result);
|
||||
$array1 = [
|
||||
[4.14],
|
||||
[4.19],
|
||||
[5.17],
|
||||
[5.77],
|
||||
[6, 39],
|
||||
];
|
||||
$array2 = [
|
||||
['red'],
|
||||
['orange'],
|
||||
['yellow'],
|
||||
['green'],
|
||||
['blue'],
|
||||
];
|
||||
$expectedResult = 'orange';
|
||||
$result = /** @scrutinizer ignore-deprecated */ LookupRef::LOOKUP(4.19, $array1, $array2);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function testGrandfatheredHlookup(): void
|
||||
{
|
||||
// Second parameter is supposed to be array of arrays.
|
||||
// Some old tests called function directly using array of strings;
|
||||
// ensure these work as before.
|
||||
$expectedResult = '#REF!';
|
||||
$result = /** @scrutinizer ignore-deprecated */ LookupRef::HLOOKUP(
|
||||
'Selection column',
|
||||
['Selection column', 'Value to retrieve'],
|
||||
5,
|
||||
false
|
||||
);
|
||||
self::assertSame($expectedResult, $result);
|
||||
$expectedResult = 'Value to retrieve';
|
||||
$result = /** @scrutinizer ignore-deprecated */ LookupRef::HLOOKUP(
|
||||
'Selection column',
|
||||
['Selection column', 'Value to retrieve'],
|
||||
2,
|
||||
false
|
||||
);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,5 +40,6 @@ class DeprecatedTest extends TestCase
|
||||
self::assertSame('ayxw', /** @scrutinizer ignore-deprecated */ TextData::SUBSTITUTE('xyxw', 'x', 'a', 1));
|
||||
self::assertSame('1', /** @scrutinizer ignore-deprecated */ TextData::CHARACTER('49'));
|
||||
self::assertSame('0', /** @scrutinizer ignore-deprecated */ TextData::CHARACTER('48'));
|
||||
self::assertSame('xyz', /** @scrutinizer ignore-deprecated */ TextData::RETURNSTRING('xyz'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ class TextSplitTest extends AllSetupTeardown
|
||||
$worksheet = $this->getSheet();
|
||||
$worksheet->getCell('A1')->setValue($text);
|
||||
$this->setDelimiterValues($worksheet, 'B', $columnDelimiter);
|
||||
$this->setDelimiterValues($worksheet, 'C', $rowDelimiter);
|
||||
if (!empty($rowDelimiter)) {
|
||||
$this->setDelimiterValues($worksheet, 'C', $rowDelimiter);
|
||||
}
|
||||
$worksheet->getCell('H1')->setValue("=TEXTSPLIT({$args})");
|
||||
|
||||
$result = Calculation::getInstance($this->getSpreadsheet())->calculateCellValue($worksheet->getCell('H1'));
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\TextData\Format;
|
||||
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||||
|
||||
class ValueToTextTest extends AllSetupTeardown
|
||||
{
|
||||
/**
|
||||
@@ -25,4 +28,15 @@ class ValueToTextTest extends AllSetupTeardown
|
||||
{
|
||||
return require 'tests/data/Calculation/TextData/VALUETOTEXT.php';
|
||||
}
|
||||
|
||||
// In Spreadsheet context, never see cell value as RichText.
|
||||
// It will use calculatedValue, which is a string.
|
||||
// Add an additional test for that condition.
|
||||
public function testRichText(): void
|
||||
{
|
||||
$richText1 = new RichText();
|
||||
$richText1->createTextRun('Hello');
|
||||
$richText1->createText(' World');
|
||||
self::assertSame('Hello World', Format::valueToText($richText1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,18 @@
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Custom;
|
||||
|
||||
use Complex\Complex;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ComplexAssert
|
||||
class ComplexAssert extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $errorMessage = '';
|
||||
|
||||
/** @var float */
|
||||
private $delta = 0.0;
|
||||
|
||||
/**
|
||||
* @param mixed $expected
|
||||
* @param mixed $actual
|
||||
@@ -40,16 +44,26 @@ class ComplexAssert
|
||||
return $adjustedDelta > 1.0 ? 1.0 : $adjustedDelta;
|
||||
}
|
||||
|
||||
public function setDelta(float $delta): self
|
||||
{
|
||||
$this->delta = $delta;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $expected
|
||||
* @param mixed $actual
|
||||
*/
|
||||
public function assertComplexEquals($expected, $actual, float $delta = 0): bool
|
||||
public function assertComplexEquals($expected, $actual, ?float $delta = null): bool
|
||||
{
|
||||
if ($expected === INF || (is_string($expected) && $expected[0] === '#')) {
|
||||
return $this->testExpectedExceptions($expected, $actual);
|
||||
}
|
||||
|
||||
if ($delta === null) {
|
||||
$delta = $this->delta;
|
||||
}
|
||||
$expectedComplex = new Complex($expected);
|
||||
$actualComplex = new Complex($actual);
|
||||
|
||||
@@ -80,4 +94,13 @@ class ComplexAssert
|
||||
{
|
||||
return $this->errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $expected
|
||||
* @param mixed $actual
|
||||
*/
|
||||
public function runAssertComplexEquals($expected, $actual, ?float $delta = null): void
|
||||
{
|
||||
self::assertTrue($this->assertComplexEquals($expected, $actual, $delta), $this->getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,16 @@ return [
|
||||
['2015-04-01', '2019-06-27'],
|
||||
0.1,
|
||||
],
|
||||
'issue 689' => [
|
||||
-0.6118824173,
|
||||
[-1000000.706, 947003.58],
|
||||
['2018-09-05', '2018-09-26'],
|
||||
],
|
||||
'issue 689 updated 2022-12-25' => [
|
||||
-0.935842486,
|
||||
[-20972.36, 20350.545],
|
||||
['2022-12-12', '2022-12-16'],
|
||||
],
|
||||
'XIRR calculation #1 is incorrect' => [
|
||||
0.137963527441025,
|
||||
[139947.43, 1893.67, 52573.25, 48849.74, 26369.16, -273029.18],
|
||||
@@ -186,13 +196,13 @@ return [
|
||||
[-10000, 3027.25, 630.68, 2018.2, 1513.62, 1765.89, 4036.33, 4036.33, 1513.62, 1513.62, 2018.16, 1513.62, 1009.08, 1513.62, 1513.62, 1765.89, 1765.89, 22421.55],
|
||||
['2000-05-24', '2000-06-05', '2001-04-09', '2004-02-24', '2005-03-18', '2006-02-15', '2007-01-10', '2007-11-14', '2008-12-17', '2010-01-15', '2011-01-14', '2012-02-03', '2013-01-18', '2014-01-24', '2015-01-30', '2016-01-22', '2017-01-20', '2017-06-05'],
|
||||
],
|
||||
'DeCampo issue5a, agree with Excel not DeCampo' => [
|
||||
'#NUM!', //-0.7640294,
|
||||
'DeCampo issue5a, agree with DeCampo not Excel' => [
|
||||
-0.7640294, // '#NUM!'
|
||||
[-2610, -2589, -5110, -2550, -5086, -2561, -5040, -2552, -2530, 29520],
|
||||
['2001-06-22', '2001-07-03', '2001-07-05', '2001-07-06', '2001-07-09', '2001-07-10', '2001-07-12', '2001-07-13', '2001-07-16', '2001-07-17'],
|
||||
],
|
||||
'DeCampo issue5b, agree with Excel not DeCampo' => [
|
||||
'#NUM!', //-0.8353404,
|
||||
'DeCampo issue5b, agree with DeCampo not Excel' => [
|
||||
-0.8353404, // '#NUM!'
|
||||
[-2610, -2589, -5110, -2550, -5086, -2561, -5040, -2552, -2530, -9840, 38900],
|
||||
['2001-06-22', '2001-07-03', '2001-07-05', '2001-07-06', '2001-07-09', '2001-07-10', '2001-07-12', '2001-07-13', '2001-07-16', '2001-07-17', '2001-07-18'],
|
||||
],
|
||||
@@ -206,8 +216,8 @@ return [
|
||||
[-2236.3994659663, -47.3417585212, -46.52619316339632, 10424.74612565936, -13.077972551952],
|
||||
['2017-12-16', '2017-12-26', '2017-12-29', '2017-12-31', '2017-12-20'],
|
||||
],
|
||||
'Python XIRR test line 39, agree with Excel not Python' => [
|
||||
'#NUM!', //-1,
|
||||
'Python XIRR test line 39, agree with Python not Excel' => [
|
||||
-1, // '#NUM!',
|
||||
[18902, 83600, -5780, -4080, -56780, -2210, -2380, 33975, 23067.98, -1619.57],
|
||||
['2016-04-06', '2016-05-04', '2016-05-12', '2017-05-08', '2017-07-03', '2018-05-07', '2019-05-06', '2019-10-01', '2020-03-13', '2020-05-07'],
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user