| Current Path : /home/emeraadmin/public_html/4d695/ |
| Current File : /home/emeraadmin/public_html/4d695/phpoffice.tar |
phpspreadsheet/src/PhpSpreadsheet/HashTable.php 0000644 00000007274 15167673464 0015672 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet;
/**
* @template T of IComparable
*/
class HashTable
{
/**
* HashTable elements.
*
* @var array<string, T>
*/
protected array $items = [];
/**
* HashTable key map.
*
* @var array<int, string>
*/
protected array $keyMap = [];
/**
* Create a new HashTable.
*
* @param T[] $source Optional source array to create HashTable from
*/
public function __construct(?array $source = [])
{
if ($source !== null) {
// Create HashTable
$this->addFromSource($source);
}
}
/**
* Add HashTable items from source.
*
* @param T[] $source Source array to create HashTable from
*/
public function addFromSource(?array $source = null): void
{
// Check if an array was passed
if ($source === null) {
return;
}
foreach ($source as $item) {
$this->add($item);
}
}
/**
* Add HashTable item.
*
* @param T $source Item to add
*/
public function add(IComparable $source): void
{
$hash = $source->getHashCode();
if (!isset($this->items[$hash])) {
$this->items[$hash] = $source;
$this->keyMap[count($this->items) - 1] = $hash;
}
}
/**
* Remove HashTable item.
*
* @param T $source Item to remove
*/
public function remove(IComparable $source): void
{
$hash = $source->getHashCode();
if (isset($this->items[$hash])) {
unset($this->items[$hash]);
$deleteKey = -1;
foreach ($this->keyMap as $key => $value) {
if ($deleteKey >= 0) {
$this->keyMap[$key - 1] = $value;
}
if ($value == $hash) {
$deleteKey = $key;
}
}
unset($this->keyMap[count($this->keyMap) - 1]);
}
}
/**
* Clear HashTable.
*/
public function clear(): void
{
$this->items = [];
$this->keyMap = [];
}
/**
* Count.
*/
public function count(): int
{
return count($this->items);
}
/**
* Get index for hash code.
*/
public function getIndexForHashCode(string $hashCode): false|int
{
return array_search($hashCode, $this->keyMap, true);
}
/**
* Get by index.
*
* @return null|T
*/
public function getByIndex(int $index): ?IComparable
{
if (isset($this->keyMap[$index])) {
return $this->getByHashCode($this->keyMap[$index]);
}
return null;
}
/**
* Get by hashcode.
*
* @return null|T
*/
public function getByHashCode(string $hashCode): ?IComparable
{
if (isset($this->items[$hashCode])) {
return $this->items[$hashCode];
}
return null;
}
/**
* HashTable to array.
*
* @return T[]
*/
public function toArray(): array
{
return $this->items;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
// each member of this class is an array
if (is_array($value)) {
$array1 = $value;
foreach ($array1 as $key1 => $value1) {
if (is_object($value1)) {
$array1[$key1] = clone $value1;
}
}
$this->$key = $array1;
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php 0000644 00000001476 15167673464 0025420 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
class Blip
{
/**
* The parent BSE.
*/
private BSE $parent;
/**
* Raw image data.
*/
private string $data;
/**
* Get the raw image data.
*/
public function getData(): string
{
return $this->data;
}
/**
* Set the raw image data.
*/
public function setData(string $data): void
{
$this->data = $data;
}
/**
* Set parent BSE.
*/
public function setParent(BSE $parent): void
{
$this->parent = $parent;
}
/**
* Get parent BSE.
*/
public function getParent(): BSE
{
return $this->parent;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php 0000644 00000003153 15167673464 0024524 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
class BSE
{
const BLIPTYPE_ERROR = 0x00;
const BLIPTYPE_UNKNOWN = 0x01;
const BLIPTYPE_EMF = 0x02;
const BLIPTYPE_WMF = 0x03;
const BLIPTYPE_PICT = 0x04;
const BLIPTYPE_JPEG = 0x05;
const BLIPTYPE_PNG = 0x06;
const BLIPTYPE_DIB = 0x07;
const BLIPTYPE_TIFF = 0x11;
const BLIPTYPE_CMYKJPEG = 0x12;
/**
* The parent BLIP Store Entry Container.
* Property is currently unused.
*/
private BstoreContainer $parent;
/**
* The BLIP (Big Large Image or Picture).
*
* @var ?BSE\Blip
*/
private ?BSE\Blip $blip = null;
/**
* The BLIP type.
*/
private int $blipType;
/**
* Set parent BLIP Store Entry Container.
*/
public function setParent(BstoreContainer $parent): void
{
$this->parent = $parent;
}
public function getParent(): BstoreContainer
{
return $this->parent;
}
/**
* Get the BLIP.
*/
public function getBlip(): ?BSE\Blip
{
return $this->blip;
}
/**
* Set the BLIP.
*/
public function setBlip(BSE\Blip $blip): void
{
$this->blip = $blip;
$blip->setParent($this);
}
/**
* Get the BLIP type.
*/
public function getBlipType(): int
{
return $this->blipType;
}
/**
* Set the BLIP type.
*/
public function setBlipType(int $blipType): void
{
$this->blipType = $blipType;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php 0000644 00000001241 15167673464 0024107 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
class BstoreContainer
{
/**
* BLIP Store Entries. Each of them holds one BLIP (Big Large Image or Picture).
*
* @var BstoreContainer\BSE[]
*/
private array $BSECollection = [];
/**
* Add a BLIP Store Entry.
*/
public function addBSE(BstoreContainer\BSE $BSE): void
{
$this->BSECollection[] = $BSE;
$BSE->setParent($this);
}
/**
* Get the collection of BLIP Store Entries.
*
* @return BstoreContainer\BSE[]
*/
public function getBSECollection(): array
{
return $this->BSECollection;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php 0000644 00000002410 15167673464 0020636 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
class DgContainer
{
/**
* Drawing index, 1-based.
*/
private ?int $dgId = null;
/**
* Last shape index in this drawing.
*/
private ?int $lastSpId = null;
private ?SpgrContainer $spgrContainer = null;
public function getDgId(): ?int
{
return $this->dgId;
}
public function setDgId(int $value): void
{
$this->dgId = $value;
}
public function getLastSpId(): ?int
{
return $this->lastSpId;
}
public function setLastSpId(int $value): void
{
$this->lastSpId = $value;
}
public function getSpgrContainer(): ?SpgrContainer
{
return $this->spgrContainer;
}
public function getSpgrContainerOrThrow(): SpgrContainer
{
if ($this->spgrContainer !== null) {
return $this->spgrContainer;
}
throw new SpreadsheetException('spgrContainer is unexpectedly null');
}
public function setSpgrContainer(SpgrContainer $spgrContainer): SpgrContainer
{
return $this->spgrContainer = $spgrContainer;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php 0000644 00000002674 15167673464 0023430 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
class SpgrContainer
{
/**
* Parent Shape Group Container.
*/
private ?self $parent = null;
/**
* Shape Container collection.
*/
private array $children = [];
/**
* Set parent Shape Group Container.
*/
public function setParent(?self $parent): void
{
$this->parent = $parent;
}
/**
* Get the parent Shape Group Container if any.
*/
public function getParent(): ?self
{
return $this->parent;
}
/**
* Add a child. This will be either spgrContainer or spContainer.
*/
public function addChild(mixed $child): void
{
$this->children[] = $child;
$child->setParent($this);
}
/**
* Get collection of Shape Containers.
*/
public function getChildren(): array
{
return $this->children;
}
/**
* Recursively get all spContainers within this spgrContainer.
*
* @return SpgrContainer\SpContainer[]
*/
public function getAllSpContainers(): array
{
$allSpContainers = [];
foreach ($this->children as $child) {
if ($child instanceof self) {
$allSpContainers = array_merge($allSpContainers, $child->getAllSpContainers());
} else {
$allSpContainers[] = $child;
}
}
return $allSpContainers;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php 0000644 00000015167 15167673464 0025656 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
class SpContainer
{
/**
* Parent Shape Group Container.
*/
private SpgrContainer $parent;
/**
* Is this a group shape?
*/
private bool $spgr = false;
/**
* Shape type.
*/
private int $spType;
/**
* Shape flag.
*/
private int $spFlag;
/**
* Shape index (usually group shape has index 0, and the rest: 1,2,3...).
*/
private int $spId;
/**
* Array of options.
*/
private array $OPT = [];
/**
* Cell coordinates of upper-left corner of shape, e.g. 'A1'.
*/
private string $startCoordinates = '';
/**
* Horizontal offset of upper-left corner of shape measured in 1/1024 of column width.
*/
private int|float $startOffsetX;
/**
* Vertical offset of upper-left corner of shape measured in 1/256 of row height.
*/
private int|float $startOffsetY;
/**
* Cell coordinates of bottom-right corner of shape, e.g. 'B2'.
*/
private string $endCoordinates;
/**
* Horizontal offset of bottom-right corner of shape measured in 1/1024 of column width.
*/
private int|float $endOffsetX;
/**
* Vertical offset of bottom-right corner of shape measured in 1/256 of row height.
*/
private int|float $endOffsetY;
/**
* Set parent Shape Group Container.
*/
public function setParent(SpgrContainer $parent): void
{
$this->parent = $parent;
}
/**
* Get the parent Shape Group Container.
*/
public function getParent(): SpgrContainer
{
return $this->parent;
}
/**
* Set whether this is a group shape.
*/
public function setSpgr(bool $value): void
{
$this->spgr = $value;
}
/**
* Get whether this is a group shape.
*/
public function getSpgr(): bool
{
return $this->spgr;
}
/**
* Set the shape type.
*/
public function setSpType(int $value): void
{
$this->spType = $value;
}
/**
* Get the shape type.
*/
public function getSpType(): int
{
return $this->spType;
}
/**
* Set the shape flag.
*/
public function setSpFlag(int $value): void
{
$this->spFlag = $value;
}
/**
* Get the shape flag.
*/
public function getSpFlag(): int
{
return $this->spFlag;
}
/**
* Set the shape index.
*/
public function setSpId(int $value): void
{
$this->spId = $value;
}
/**
* Get the shape index.
*/
public function getSpId(): int
{
return $this->spId;
}
/**
* Set an option for the Shape Group Container.
*
* @param int $property The number specifies the option
*/
public function setOPT(int $property, mixed $value): void
{
$this->OPT[$property] = $value;
}
/**
* Get an option for the Shape Group Container.
*
* @param int $property The number specifies the option
*/
public function getOPT(int $property): mixed
{
if (isset($this->OPT[$property])) {
return $this->OPT[$property];
}
return null;
}
/**
* Get the collection of options.
*/
public function getOPTCollection(): array
{
return $this->OPT;
}
/**
* Set cell coordinates of upper-left corner of shape.
*
* @param string $value eg: 'A1'
*/
public function setStartCoordinates(string $value): void
{
$this->startCoordinates = $value;
}
/**
* Get cell coordinates of upper-left corner of shape.
*/
public function getStartCoordinates(): string
{
return $this->startCoordinates;
}
/**
* Set offset in x-direction of upper-left corner of shape measured in 1/1024 of column width.
*/
public function setStartOffsetX(int|float $startOffsetX): void
{
$this->startOffsetX = $startOffsetX;
}
/**
* Get offset in x-direction of upper-left corner of shape measured in 1/1024 of column width.
*/
public function getStartOffsetX(): int|float
{
return $this->startOffsetX;
}
/**
* Set offset in y-direction of upper-left corner of shape measured in 1/256 of row height.
*/
public function setStartOffsetY(int|float $startOffsetY): void
{
$this->startOffsetY = $startOffsetY;
}
/**
* Get offset in y-direction of upper-left corner of shape measured in 1/256 of row height.
*/
public function getStartOffsetY(): int|float
{
return $this->startOffsetY;
}
/**
* Set cell coordinates of bottom-right corner of shape.
*
* @param string $value eg: 'A1'
*/
public function setEndCoordinates(string $value): void
{
$this->endCoordinates = $value;
}
/**
* Get cell coordinates of bottom-right corner of shape.
*/
public function getEndCoordinates(): string
{
return $this->endCoordinates;
}
/**
* Set offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width.
*/
public function setEndOffsetX(int|float $endOffsetX): void
{
$this->endOffsetX = $endOffsetX;
}
/**
* Get offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width.
*/
public function getEndOffsetX(): int|float
{
return $this->endOffsetX;
}
/**
* Set offset in y-direction of bottom-right corner of shape measured in 1/256 of row height.
*/
public function setEndOffsetY(int|float $endOffsetY): void
{
$this->endOffsetY = $endOffsetY;
}
/**
* Get offset in y-direction of bottom-right corner of shape measured in 1/256 of row height.
*/
public function getEndOffsetY(): int|float
{
return $this->endOffsetY;
}
/**
* Get the nesting level of this spContainer. This is the number of spgrContainers between this spContainer and
* the dgContainer. A value of 1 = immediately within first spgrContainer
* Higher nesting level occurs if and only if spContainer is part of a shape group.
*
* @return int Nesting level
*/
public function getNestingLevel(): int
{
$nestingLevel = 0;
$parent = $this->getParent();
while ($parent instanceof SpgrContainer) {
++$nestingLevel;
$parent = $parent->getParent();
}
return $nestingLevel;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php 0000644 00000005711 15167673464 0021014 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher;
class DggContainer
{
/**
* Maximum shape index of all shapes in all drawings increased by one.
*/
private int $spIdMax;
/**
* Total number of drawings saved.
*/
private int $cDgSaved;
/**
* Total number of shapes saved (including group shapes).
*/
private int $cSpSaved;
/**
* BLIP Store Container.
*
* @var ?DggContainer\BstoreContainer
*/
private ?DggContainer\BstoreContainer $bstoreContainer = null;
/**
* Array of options for the drawing group.
*/
private array $OPT = [];
/**
* Array of identifier clusters containg information about the maximum shape identifiers.
*/
private array $IDCLs = [];
/**
* Get maximum shape index of all shapes in all drawings (plus one).
*/
public function getSpIdMax(): int
{
return $this->spIdMax;
}
/**
* Set maximum shape index of all shapes in all drawings (plus one).
*/
public function setSpIdMax(int $value): void
{
$this->spIdMax = $value;
}
/**
* Get total number of drawings saved.
*/
public function getCDgSaved(): int
{
return $this->cDgSaved;
}
/**
* Set total number of drawings saved.
*/
public function setCDgSaved(int $value): void
{
$this->cDgSaved = $value;
}
/**
* Get total number of shapes saved (including group shapes).
*/
public function getCSpSaved(): int
{
return $this->cSpSaved;
}
/**
* Set total number of shapes saved (including group shapes).
*/
public function setCSpSaved(int $value): void
{
$this->cSpSaved = $value;
}
/**
* Get BLIP Store Container.
*/
public function getBstoreContainer(): ?DggContainer\BstoreContainer
{
return $this->bstoreContainer;
}
/**
* Set BLIP Store Container.
*/
public function setBstoreContainer(DggContainer\BstoreContainer $bstoreContainer): void
{
$this->bstoreContainer = $bstoreContainer;
}
/**
* Set an option for the drawing group.
*
* @param int $property The number specifies the option
*/
public function setOPT(int $property, mixed $value): void
{
$this->OPT[$property] = $value;
}
/**
* Get an option for the drawing group.
*
* @param int $property The number specifies the option
*/
public function getOPT(int $property): mixed
{
if (isset($this->OPT[$property])) {
return $this->OPT[$property];
}
return null;
}
/**
* Get identifier clusters.
*/
public function getIDCLs(): array
{
return $this->IDCLs;
}
/**
* Set identifier clusters. [<drawingId> => <max shape id>, ...].
*/
public function setIDCLs(array $IDCLs): void
{
$this->IDCLs = $IDCLs;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php 0000644 00000026167 15167673464 0017663 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
abstract class BestFit
{
/**
* Indicator flag for a calculation error.
*/
protected bool $error = false;
/**
* Algorithm type to use for best-fit.
*/
protected string $bestFitType = 'undetermined';
/**
* Number of entries in the sets of x- and y-value arrays.
*/
protected int $valueCount;
/**
* X-value dataseries of values.
*
* @var float[]
*/
protected array $xValues = [];
/**
* Y-value dataseries of values.
*
* @var float[]
*/
protected array $yValues = [];
/**
* Flag indicating whether values should be adjusted to Y=0.
*/
protected bool $adjustToZero = false;
/**
* Y-value series of best-fit values.
*
* @var float[]
*/
protected array $yBestFitValues = [];
protected float $goodnessOfFit = 1;
protected float $stdevOfResiduals = 0;
protected float $covariance = 0;
protected float $correlation = 0;
protected float $SSRegression = 0;
protected float $SSResiduals = 0;
protected float $DFResiduals = 0;
protected float $f = 0;
protected float $slope = 0;
protected float $slopeSE = 0;
protected float $intersect = 0;
protected float $intersectSE = 0;
protected float $xOffset = 0;
protected float $yOffset = 0;
public function getError(): bool
{
return $this->error;
}
public function getBestFitType(): string
{
return $this->bestFitType;
}
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
abstract public function getValueOfYForX(float $xValue): float;
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
abstract public function getValueOfXForY(float $yValue): float;
/**
* Return the original set of X-Values.
*
* @return float[] X-Values
*/
public function getXValues(): array
{
return $this->xValues;
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*/
abstract public function getEquation(int $dp = 0): string;
/**
* Return the Slope of the line.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getSlope(int $dp = 0): float
{
if ($dp != 0) {
return round($this->slope, $dp);
}
return $this->slope;
}
/**
* Return the standard error of the Slope.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getSlopeSE(int $dp = 0): float
{
if ($dp != 0) {
return round($this->slopeSE, $dp);
}
return $this->slopeSE;
}
/**
* Return the Value of X where it intersects Y = 0.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getIntersect(int $dp = 0): float
{
if ($dp != 0) {
return round($this->intersect, $dp);
}
return $this->intersect;
}
/**
* Return the standard error of the Intersect.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getIntersectSE(int $dp = 0): float
{
if ($dp != 0) {
return round($this->intersectSE, $dp);
}
return $this->intersectSE;
}
/**
* Return the goodness of fit for this regression.
*
* @param int $dp Number of places of decimal precision to return
*/
public function getGoodnessOfFit(int $dp = 0): float
{
if ($dp != 0) {
return round($this->goodnessOfFit, $dp);
}
return $this->goodnessOfFit;
}
/**
* Return the goodness of fit for this regression.
*
* @param int $dp Number of places of decimal precision to return
*/
public function getGoodnessOfFitPercent(int $dp = 0): float
{
if ($dp != 0) {
return round($this->goodnessOfFit * 100, $dp);
}
return $this->goodnessOfFit * 100;
}
/**
* Return the standard deviation of the residuals for this regression.
*
* @param int $dp Number of places of decimal precision to return
*/
public function getStdevOfResiduals(int $dp = 0): float
{
if ($dp != 0) {
return round($this->stdevOfResiduals, $dp);
}
return $this->stdevOfResiduals;
}
/**
* @param int $dp Number of places of decimal precision to return
*/
public function getSSRegression(int $dp = 0): float
{
if ($dp != 0) {
return round($this->SSRegression, $dp);
}
return $this->SSRegression;
}
/**
* @param int $dp Number of places of decimal precision to return
*/
public function getSSResiduals(int $dp = 0): float
{
if ($dp != 0) {
return round($this->SSResiduals, $dp);
}
return $this->SSResiduals;
}
/**
* @param int $dp Number of places of decimal precision to return
*/
public function getDFResiduals(int $dp = 0): float
{
if ($dp != 0) {
return round($this->DFResiduals, $dp);
}
return $this->DFResiduals;
}
/**
* @param int $dp Number of places of decimal precision to return
*/
public function getF(int $dp = 0): float
{
if ($dp != 0) {
return round($this->f, $dp);
}
return $this->f;
}
/**
* @param int $dp Number of places of decimal precision to return
*/
public function getCovariance(int $dp = 0): float
{
if ($dp != 0) {
return round($this->covariance, $dp);
}
return $this->covariance;
}
/**
* @param int $dp Number of places of decimal precision to return
*/
public function getCorrelation(int $dp = 0): float
{
if ($dp != 0) {
return round($this->correlation, $dp);
}
return $this->correlation;
}
/**
* @return float[]
*/
public function getYBestFitValues(): array
{
return $this->yBestFitValues;
}
protected function calculateGoodnessOfFit(float $sumX, float $sumY, float $sumX2, float $sumY2, float $sumXY, float $meanX, float $meanY, bool|int $const): void
{
$SSres = $SScov = $SStot = $SSsex = 0.0;
foreach ($this->xValues as $xKey => $xValue) {
$bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
$SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY);
if ($const === true) {
$SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY);
} else {
$SStot += $this->yValues[$xKey] * $this->yValues[$xKey];
}
$SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY);
if ($const === true) {
$SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX);
} else {
$SSsex += $this->xValues[$xKey] * $this->xValues[$xKey];
}
}
$this->SSResiduals = $SSres;
$this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0);
if ($this->DFResiduals == 0.0) {
$this->stdevOfResiduals = 0.0;
} else {
$this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals);
}
if ($SStot == 0.0 || $SSres == $SStot) {
$this->goodnessOfFit = 1;
} else {
$this->goodnessOfFit = 1 - ($SSres / $SStot);
}
$this->SSRegression = $this->goodnessOfFit * $SStot;
$this->covariance = $SScov / $this->valueCount;
$this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2));
$this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex);
$this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2));
if ($this->SSResiduals != 0.0) {
if ($this->DFResiduals == 0.0) {
$this->f = 0.0;
} else {
$this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals);
}
} else {
if ($this->DFResiduals == 0.0) {
$this->f = 0.0;
} else {
$this->f = $this->SSRegression / $this->DFResiduals;
}
}
}
/** @return float|int */
private function sumSquares(array $values)
{
return array_sum(
array_map(
fn ($value): float|int => $value ** 2,
$values
)
);
}
/**
* @param float[] $yValues
* @param float[] $xValues
*/
protected function leastSquareFit(array $yValues, array $xValues, bool $const): void
{
// calculate sums
$sumValuesX = array_sum($xValues);
$sumValuesY = array_sum($yValues);
$meanValueX = $sumValuesX / $this->valueCount;
$meanValueY = $sumValuesY / $this->valueCount;
$sumSquaresX = $this->sumSquares($xValues);
$sumSquaresY = $this->sumSquares($yValues);
$mBase = $mDivisor = 0.0;
$xy_sum = 0.0;
for ($i = 0; $i < $this->valueCount; ++$i) {
$xy_sum += $xValues[$i] * $yValues[$i];
if ($const === true) {
$mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY);
$mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX);
} else {
$mBase += $xValues[$i] * $yValues[$i];
$mDivisor += $xValues[$i] * $xValues[$i];
}
}
// calculate slope
$this->slope = $mBase / $mDivisor;
// calculate intersect
$this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0;
$this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const);
}
/**
* Define the regression.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
public function __construct(array $yValues, array $xValues = [])
{
// Calculate number of points
$yValueCount = count($yValues);
$xValueCount = count($xValues);
// Define X Values if necessary
if ($xValueCount === 0) {
$xValues = range(1, $yValueCount);
} elseif ($yValueCount !== $xValueCount) {
// Ensure both arrays of points are the same size
$this->error = true;
}
$this->valueCount = $yValueCount;
$this->xValues = $xValues;
$this->yValues = $yValues;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php 0000644 00000004443 15167673464 0022037 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
class LogarithmicBestFit extends BestFit
{
/**
* Algorithm type to use for best-fit
* (Name of this Trend class).
*/
protected string $bestFitType = 'logarithmic';
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
public function getValueOfYForX(float $xValue): float
{
return $this->getIntersect() + $this->getSlope() * log($xValue - $this->xOffset);
}
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
public function getValueOfXForY(float $yValue): float
{
return exp(($yValue - $this->getIntersect()) / $this->getSlope());
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getEquation(int $dp = 0): string
{
$slope = $this->getSlope($dp);
$intersect = $this->getIntersect($dp);
return 'Y = ' . $slope . ' * log(' . $intersect . ' * X)';
}
/**
* Execute the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
private function logarithmicRegression(array $yValues, array $xValues, bool $const): void
{
$adjustedYValues = array_map(
fn ($value): float => ($value < 0.0) ? 0 - log(abs($value)) : log($value),
$yValues
);
$this->leastSquareFit($adjustedYValues, $xValues, $const);
}
/**
* Define the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
public function __construct(array $yValues, array $xValues = [], bool $const = true)
{
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->logarithmicRegression($yValues, $xValues, (bool) $const);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php 0000644 00000004111 15167673464 0020777 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
class LinearBestFit extends BestFit
{
/**
* Algorithm type to use for best-fit
* (Name of this Trend class).
*/
protected string $bestFitType = 'linear';
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
public function getValueOfYForX(float $xValue): float
{
return $this->getIntersect() + $this->getSlope() * $xValue;
}
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
public function getValueOfXForY(float $yValue): float
{
return ($yValue - $this->getIntersect()) / $this->getSlope();
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getEquation(int $dp = 0): string
{
$slope = $this->getSlope($dp);
$intersect = $this->getIntersect($dp);
return 'Y = ' . $intersect . ' + ' . $slope . ' * X';
}
/**
* Execute the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
private function linearRegression(array $yValues, array $xValues, bool $const): void
{
$this->leastSquareFit($yValues, $xValues, $const);
}
/**
* Define the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
public function __construct(array $yValues, array $xValues = [], bool $const = true)
{
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->linearRegression($yValues, $xValues, (bool) $const);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php 0000644 00000011445 15167673464 0017370 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
class Trend
{
const TREND_LINEAR = 'Linear';
const TREND_LOGARITHMIC = 'Logarithmic';
const TREND_EXPONENTIAL = 'Exponential';
const TREND_POWER = 'Power';
const TREND_POLYNOMIAL_2 = 'Polynomial_2';
const TREND_POLYNOMIAL_3 = 'Polynomial_3';
const TREND_POLYNOMIAL_4 = 'Polynomial_4';
const TREND_POLYNOMIAL_5 = 'Polynomial_5';
const TREND_POLYNOMIAL_6 = 'Polynomial_6';
const TREND_BEST_FIT = 'Bestfit';
const TREND_BEST_FIT_NO_POLY = 'Bestfit_no_Polynomials';
/**
* Names of the best-fit Trend analysis methods.
*
* @var string[]
*/
private static array $trendTypes = [
self::TREND_LINEAR,
self::TREND_LOGARITHMIC,
self::TREND_EXPONENTIAL,
self::TREND_POWER,
];
/**
* Names of the best-fit Trend polynomial orders.
*
* @var string[]
*/
private static array $trendTypePolynomialOrders = [
self::TREND_POLYNOMIAL_2,
self::TREND_POLYNOMIAL_3,
self::TREND_POLYNOMIAL_4,
self::TREND_POLYNOMIAL_5,
self::TREND_POLYNOMIAL_6,
];
/**
* Cached results for each method when trying to identify which provides the best fit.
*
* @var BestFit[]
*/
private static array $trendCache = [];
public static function calculate(string $trendType = self::TREND_BEST_FIT, array $yValues = [], array $xValues = [], bool $const = true): mixed
{
// Calculate number of points in each dataset
$nY = count($yValues);
$nX = count($xValues);
// Define X Values if necessary
if ($nX === 0) {
$xValues = range(1, $nY);
} elseif ($nY !== $nX) {
// Ensure both arrays of points are the same size
trigger_error('Trend(): Number of elements in coordinate arrays do not match.', E_USER_ERROR);
}
$key = md5($trendType . $const . serialize($yValues) . serialize($xValues));
// Determine which Trend method has been requested
switch ($trendType) {
// Instantiate and return the class for the requested Trend method
case self::TREND_LINEAR:
case self::TREND_LOGARITHMIC:
case self::TREND_EXPONENTIAL:
case self::TREND_POWER:
if (!isset(self::$trendCache[$key])) {
$className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit';
self::$trendCache[$key] = new $className($yValues, $xValues, $const);
}
return self::$trendCache[$key];
case self::TREND_POLYNOMIAL_2:
case self::TREND_POLYNOMIAL_3:
case self::TREND_POLYNOMIAL_4:
case self::TREND_POLYNOMIAL_5:
case self::TREND_POLYNOMIAL_6:
if (!isset(self::$trendCache[$key])) {
$order = (int) substr($trendType, -1);
self::$trendCache[$key] = new PolynomialBestFit($order, $yValues, $xValues);
}
return self::$trendCache[$key];
case self::TREND_BEST_FIT:
case self::TREND_BEST_FIT_NO_POLY:
// If the request is to determine the best fit regression, then we test each Trend line in turn
// Start by generating an instance of each available Trend method
$bestFit = [];
$bestFitValue = [];
foreach (self::$trendTypes as $trendMethod) {
$className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit';
//* @phpstan-ignore-next-line
$bestFit[$trendMethod] = new $className($yValues, $xValues, $const);
$bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit();
}
if ($trendType != self::TREND_BEST_FIT_NO_POLY) {
foreach (self::$trendTypePolynomialOrders as $trendMethod) {
$order = (int) substr($trendMethod, -1);
$bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues);
if ($bestFit[$trendMethod]->getError()) {
unset($bestFit[$trendMethod]);
} else {
$bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit();
}
}
}
// Determine which of our Trend lines is the best fit, and then we return the instance of that Trend class
arsort($bestFitValue);
$bestFitType = key($bestFitValue);
return $bestFit[$bestFitType];
default:
return false;
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php 0000644 00000005677 15167673464 0022075 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
class ExponentialBestFit extends BestFit
{
/**
* Algorithm type to use for best-fit
* (Name of this Trend class).
*/
protected string $bestFitType = 'exponential';
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
public function getValueOfYForX(float $xValue): float
{
return $this->getIntersect() * $this->getSlope() ** ($xValue - $this->xOffset);
}
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
public function getValueOfXForY(float $yValue): float
{
return log(($yValue + $this->yOffset) / $this->getIntersect()) / log($this->getSlope());
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getEquation(int $dp = 0): string
{
$slope = $this->getSlope($dp);
$intersect = $this->getIntersect($dp);
return 'Y = ' . $intersect . ' * ' . $slope . '^X';
}
/**
* Return the Slope of the line.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getSlope(int $dp = 0): float
{
if ($dp != 0) {
return round(exp($this->slope), $dp);
}
return exp($this->slope);
}
/**
* Return the Value of X where it intersects Y = 0.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getIntersect(int $dp = 0): float
{
if ($dp != 0) {
return round(exp($this->intersect), $dp);
}
return exp($this->intersect);
}
/**
* Execute the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
private function exponentialRegression(array $yValues, array $xValues, bool $const): void
{
$adjustedYValues = array_map(
fn ($value): float => ($value < 0.0) ? 0 - log(abs($value)) : log($value),
$yValues
);
$this->leastSquareFit($adjustedYValues, $xValues, $const);
}
/**
* Define the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
public function __construct(array $yValues, array $xValues = [], bool $const = true)
{
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->exponentialRegression($yValues, $xValues, (bool) $const);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php 0000644 00000005416 15167673464 0020672 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
class PowerBestFit extends BestFit
{
/**
* Algorithm type to use for best-fit
* (Name of this Trend class).
*/
protected string $bestFitType = 'power';
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
public function getValueOfYForX(float $xValue): float
{
return $this->getIntersect() * ($xValue - $this->xOffset) ** $this->getSlope();
}
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
public function getValueOfXForY(float $yValue): float
{
return (($yValue + $this->yOffset) / $this->getIntersect()) ** (1 / $this->getSlope());
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getEquation(int $dp = 0): string
{
$slope = $this->getSlope($dp);
$intersect = $this->getIntersect($dp);
return 'Y = ' . $intersect . ' * X^' . $slope;
}
/**
* Return the Value of X where it intersects Y = 0.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getIntersect(int $dp = 0): float
{
if ($dp != 0) {
return round(exp($this->intersect), $dp);
}
return exp($this->intersect);
}
/**
* Execute the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
private function powerRegression(array $yValues, array $xValues, bool $const): void
{
$adjustedYValues = array_map(
fn ($value): float => ($value < 0.0) ? 0 - log(abs($value)) : log($value),
$yValues
);
$adjustedXValues = array_map(
fn ($value): float => ($value < 0.0) ? 0 - log(abs($value)) : log($value),
$xValues
);
$this->leastSquareFit($adjustedYValues, $adjustedXValues, $const);
}
/**
* Define the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
public function __construct(array $yValues, array $xValues = [], bool $const = true)
{
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->powerRegression($yValues, $xValues, (bool) $const);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php 0000644 00000014260 15167673464 0021716 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
use Matrix\Matrix;
// Phpstan and Scrutinizer seem to have legitimate complaints.
// $this->slope is specified where an array is expected in several places.
// But it seems that it should always be float.
// This code is probably not exercised at all in unit tests.
class PolynomialBestFit extends BestFit
{
/**
* Algorithm type to use for best-fit
* (Name of this Trend class).
*/
protected string $bestFitType = 'polynomial';
/**
* Polynomial order.
*/
protected int $order = 0;
/**
* Return the order of this polynomial.
*/
public function getOrder(): int
{
return $this->order;
}
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
public function getValueOfYForX(float $xValue): float
{
$retVal = $this->getIntersect();
$slope = $this->getSlope();
// Phpstan and Scrutinizer are both correct - getSlope returns float, not array.
// @phpstan-ignore-next-line
foreach ($slope as $key => $value) {
if ($value != 0.0) {
$retVal += $value * $xValue ** ($key + 1);
}
}
return $retVal;
}
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
public function getValueOfXForY(float $yValue): float
{
return ($yValue - $this->getIntersect()) / $this->getSlope();
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getEquation(int $dp = 0): string
{
$slope = $this->getSlope($dp);
$intersect = $this->getIntersect($dp);
$equation = 'Y = ' . $intersect;
// Phpstan and Scrutinizer are both correct - getSlope returns float, not array.
// @phpstan-ignore-next-line
foreach ($slope as $key => $value) {
if ($value != 0.0) {
$equation .= ' + ' . $value . ' * X';
if ($key > 0) {
$equation .= '^' . ($key + 1);
}
}
}
return $equation;
}
/**
* Return the Slope of the line.
*
* @param int $dp Number of places of decimal precision to display
*/
public function getSlope(int $dp = 0): float
{
if ($dp != 0) {
$coefficients = [];
//* @phpstan-ignore-next-line
foreach ($this->slope as $coefficient) {
$coefficients[] = round($coefficient, $dp);
}
// @phpstan-ignore-next-line
return $coefficients;
}
return $this->slope;
}
public function getCoefficients(int $dp = 0): array
{
// Phpstan and Scrutinizer are both correct - getSlope returns float, not array.
// @phpstan-ignore-next-line
return array_merge([$this->getIntersect($dp)], $this->getSlope($dp));
}
/**
* Execute the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param int $order Order of Polynomial for this regression
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
private function polynomialRegression(int $order, array $yValues, array $xValues): void
{
// calculate sums
$x_sum = array_sum($xValues);
$y_sum = array_sum($yValues);
$xx_sum = $xy_sum = $yy_sum = 0;
for ($i = 0; $i < $this->valueCount; ++$i) {
$xy_sum += $xValues[$i] * $yValues[$i];
$xx_sum += $xValues[$i] * $xValues[$i];
$yy_sum += $yValues[$i] * $yValues[$i];
}
/*
* This routine uses logic from the PHP port of polyfit version 0.1
* written by Michael Bommarito and Paul Meagher
*
* The function fits a polynomial function of order $order through
* a series of x-y data points using least squares.
*
*/
$A = [];
$B = [];
for ($i = 0; $i < $this->valueCount; ++$i) {
for ($j = 0; $j <= $order; ++$j) {
$A[$i][$j] = $xValues[$i] ** $j;
}
}
for ($i = 0; $i < $this->valueCount; ++$i) {
$B[$i] = [$yValues[$i]];
}
$matrixA = new Matrix($A);
$matrixB = new Matrix($B);
$C = $matrixA->solve($matrixB);
$coefficients = [];
for ($i = 0; $i < $C->rows; ++$i) {
$r = $C->getValue($i + 1, 1); // row and column are origin-1
if (abs($r) <= 10 ** (-9)) {
$r = 0;
}
$coefficients[] = $r;
}
$this->intersect = array_shift($coefficients);
// Phpstan is correct
//* @phpstan-ignore-next-line
$this->slope = $coefficients;
$this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, 0, 0, 0);
foreach ($this->xValues as $xKey => $xValue) {
$this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
}
}
/**
* Define the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param int $order Order of Polynomial for this regression
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
public function __construct(int $order, array $yValues, array $xValues = [])
{
parent::__construct($yValues, $xValues);
if (!$this->error) {
if ($order < $this->valueCount) {
$this->bestFitType .= '_' . $order;
$this->order = $order;
$this->polynomialRegression($order, $yValues, $xValues);
if (($this->getGoodnessOfFit() < 0.0) || ($this->getGoodnessOfFit() > 1.0)) {
$this->error = true;
}
} else {
$this->error = true;
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php 0000644 00000042634 15167673464 0015663 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | Based on OLE::Storage_Lite by Kawai, Takanori |
// +----------------------------------------------------------------------+
//
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream;
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
/*
* Array for storing OLE instances that are accessed from
* OLE_ChainedBlockStream::stream_open().
*
* @var array
*/
$GLOBALS['_OLE_INSTANCES'] = [];
/**
* OLE package base class.
*
* @author Xavier Noguer <xnoguer@php.net>
* @author Christian Schmidt <schmidt@php.net>
*/
class OLE
{
const OLE_PPS_TYPE_ROOT = 5;
const OLE_PPS_TYPE_DIR = 1;
const OLE_PPS_TYPE_FILE = 2;
const OLE_DATA_SIZE_SMALL = 0x1000;
const OLE_LONG_INT_SIZE = 4;
const OLE_PPS_SIZE = 0x80;
/**
* The file handle for reading an OLE container.
*
* @var resource
*/
public $_file_handle;
/**
* Array of PPS's found on the OLE container.
*/
public array $_list = [];
/**
* Root directory of OLE container.
*/
public Root $root;
/**
* Big Block Allocation Table.
*
* @var array (blockId => nextBlockId)
*/
public array $bbat;
/**
* Short Block Allocation Table.
*
* @var array (blockId => nextBlockId)
*/
public array $sbat;
/**
* Size of big blocks. This is usually 512.
*
* @var int number of octets per block
*/
public int $bigBlockSize;
/**
* Size of small blocks. This is usually 64.
*
* @var int number of octets per block
*/
public int $smallBlockSize;
/**
* Threshold for big blocks.
*/
public int $bigBlockThreshold;
/**
* Reads an OLE container from the contents of the file given.
*
* @acces public
*
* @return bool true on success, PEAR_Error on failure
*/
public function read(string $filename): bool
{
$fh = @fopen($filename, 'rb');
if ($fh === false) {
throw new ReaderException("Can't open file $filename");
}
$this->_file_handle = $fh;
$signature = fread($fh, 8);
if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
throw new ReaderException("File doesn't seem to be an OLE container.");
}
fseek($fh, 28);
if (fread($fh, 2) != "\xFE\xFF") {
// This shouldn't be a problem in practice
throw new ReaderException('Only Little-Endian encoding is supported.');
}
// Size of blocks and short blocks in bytes
$this->bigBlockSize = 2 ** self::readInt2($fh);
$this->smallBlockSize = 2 ** self::readInt2($fh);
// Skip UID, revision number and version number
fseek($fh, 44);
// Number of blocks in Big Block Allocation Table
$bbatBlockCount = self::readInt4($fh);
// Root chain 1st block
$directoryFirstBlockId = self::readInt4($fh);
// Skip unused bytes
fseek($fh, 56);
// Streams shorter than this are stored using small blocks
$this->bigBlockThreshold = self::readInt4($fh);
// Block id of first sector in Short Block Allocation Table
$sbatFirstBlockId = self::readInt4($fh);
// Number of blocks in Short Block Allocation Table
$sbbatBlockCount = self::readInt4($fh);
// Block id of first sector in Master Block Allocation Table
$mbatFirstBlockId = self::readInt4($fh);
// Number of blocks in Master Block Allocation Table
$mbbatBlockCount = self::readInt4($fh);
$this->bbat = [];
// Remaining 4 * 109 bytes of current block is beginning of Master
// Block Allocation Table
$mbatBlocks = [];
for ($i = 0; $i < 109; ++$i) {
$mbatBlocks[] = self::readInt4($fh);
}
// Read rest of Master Block Allocation Table (if any is left)
$pos = $this->getBlockOffset($mbatFirstBlockId);
for ($i = 0; $i < $mbbatBlockCount; ++$i) {
fseek($fh, $pos);
for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) {
$mbatBlocks[] = self::readInt4($fh);
}
// Last block id in each block points to next block
$pos = $this->getBlockOffset(self::readInt4($fh));
}
// Read Big Block Allocation Table according to chain specified by $mbatBlocks
for ($i = 0; $i < $bbatBlockCount; ++$i) {
$pos = $this->getBlockOffset($mbatBlocks[$i]);
fseek($fh, $pos);
for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) {
$this->bbat[] = self::readInt4($fh);
}
}
// Read short block allocation table (SBAT)
$this->sbat = [];
$shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4;
$sbatFh = $this->getStream($sbatFirstBlockId);
for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) {
$this->sbat[$blockId] = self::readInt4($sbatFh);
}
fclose($sbatFh);
$this->readPpsWks($directoryFirstBlockId);
return true;
}
/**
* @param int $blockId byte offset from beginning of file
*/
public function getBlockOffset(int $blockId): int
{
return 512 + $blockId * $this->bigBlockSize;
}
/**
* Returns a stream for use with fread() etc. External callers should
* use \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File::getStream().
*
* @param int|OLE\PPS $blockIdOrPps block id or PPS
*
* @return resource read-only stream
*/
public function getStream($blockIdOrPps)
{
static $isRegistered = false;
if (!$isRegistered) {
stream_wrapper_register('ole-chainedblockstream', ChainedBlockStream::class);
$isRegistered = true;
}
// Store current instance in global array, so that it can be accessed
// in OLE_ChainedBlockStream::stream_open().
// Object is removed from self::$instances in OLE_Stream::close().
$GLOBALS['_OLE_INSTANCES'][] = $this;
$keys = array_keys($GLOBALS['_OLE_INSTANCES']);
$instanceId = end($keys);
$path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;
if ($blockIdOrPps instanceof OLE\PPS) {
$path .= '&blockId=' . $blockIdOrPps->startBlock;
$path .= '&size=' . $blockIdOrPps->Size;
} else {
$path .= '&blockId=' . $blockIdOrPps;
}
$resource = fopen($path, 'rb');
if ($resource === false) {
throw new Exception("Unable to open stream $path");
}
return $resource;
}
/**
* Reads a signed char.
*
* @param resource $fileHandle file handle
*/
private static function readInt1($fileHandle): int
{
[, $tmp] = unpack('c', fread($fileHandle, 1) ?: '') ?: [0, 0];
return $tmp;
}
/**
* Reads an unsigned short (2 octets).
*
* @param resource $fileHandle file handle
*/
private static function readInt2($fileHandle): int
{
[, $tmp] = unpack('v', fread($fileHandle, 2) ?: '') ?: [0, 0];
return $tmp;
}
private const SIGNED_4OCTET_LIMIT = 2147483648;
private const SIGNED_4OCTET_SUBTRACT = 2 * self::SIGNED_4OCTET_LIMIT;
/**
* Reads long (4 octets), interpreted as if signed on 32-bit system.
*
* @param resource $fileHandle file handle
*/
private static function readInt4($fileHandle): int
{
[, $tmp] = unpack('V', fread($fileHandle, 4) ?: '') ?: [0, 0];
if ($tmp >= self::SIGNED_4OCTET_LIMIT) {
$tmp -= self::SIGNED_4OCTET_SUBTRACT;
}
return $tmp;
}
/**
* Gets information about all PPS's on the OLE container from the PPS WK's
* creates an OLE_PPS object for each one.
*
* @param int $blockId the block id of the first block
*
* @return bool true on success, PEAR_Error on failure
*/
public function readPpsWks(int $blockId): bool
{
$fh = $this->getStream($blockId);
for ($pos = 0; true; $pos += 128) {
fseek($fh, $pos, SEEK_SET);
$nameUtf16 = (string) fread($fh, 64);
$nameLength = self::readInt2($fh);
$nameUtf16 = substr($nameUtf16, 0, $nameLength - 2);
// Simple conversion from UTF-16LE to ISO-8859-1
$name = str_replace("\x00", '', $nameUtf16);
$type = self::readInt1($fh);
switch ($type) {
case self::OLE_PPS_TYPE_ROOT:
$pps = new Root(null, null, []);
$this->root = $pps;
break;
case self::OLE_PPS_TYPE_DIR:
$pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []);
break;
case self::OLE_PPS_TYPE_FILE:
$pps = new OLE\PPS\File($name);
break;
default:
throw new Exception('Unsupported PPS type');
}
fseek($fh, 1, SEEK_CUR);
$pps->Type = $type;
$pps->Name = $name;
$pps->PrevPps = self::readInt4($fh);
$pps->NextPps = self::readInt4($fh);
$pps->DirPps = self::readInt4($fh);
fseek($fh, 20, SEEK_CUR);
$pps->Time1st = self::OLE2LocalDate((string) fread($fh, 8));
$pps->Time2nd = self::OLE2LocalDate((string) fread($fh, 8));
$pps->startBlock = self::readInt4($fh);
$pps->Size = self::readInt4($fh);
$pps->No = count($this->_list);
$this->_list[] = $pps;
// check if the PPS tree (starting from root) is complete
if (isset($this->root) && $this->ppsTreeComplete($this->root->No)) {
break;
}
}
fclose($fh);
// Initialize $pps->children on directories
foreach ($this->_list as $pps) {
if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) {
$nos = [$pps->DirPps];
$pps->children = [];
while (!empty($nos)) {
$no = array_pop($nos);
if ($no != -1) {
$childPps = $this->_list[$no];
$nos[] = $childPps->PrevPps;
$nos[] = $childPps->NextPps;
$pps->children[] = $childPps;
}
}
}
}
return true;
}
/**
* It checks whether the PPS tree is complete (all PPS's read)
* starting with the given PPS (not necessarily root).
*
* @param int $index The index of the PPS from which we are checking
*
* @return bool Whether the PPS tree for the given PPS is complete
*/
private function ppsTreeComplete(int $index): bool
{
return isset($this->_list[$index])
&& ($pps = $this->_list[$index])
&& ($pps->PrevPps == -1
|| $this->ppsTreeComplete($pps->PrevPps))
&& ($pps->NextPps == -1
|| $this->ppsTreeComplete($pps->NextPps))
&& ($pps->DirPps == -1
|| $this->ppsTreeComplete($pps->DirPps));
}
/**
* Checks whether a PPS is a File PPS or not.
* If there is no PPS for the index given, it will return false.
*
* @param int $index The index for the PPS
*
* @return bool true if it's a File PPS, false otherwise
*/
public function isFile(int $index): bool
{
if (isset($this->_list[$index])) {
return $this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE;
}
return false;
}
/**
* Checks whether a PPS is a Root PPS or not.
* If there is no PPS for the index given, it will return false.
*
* @param int $index the index for the PPS
*
* @return bool true if it's a Root PPS, false otherwise
*/
public function isRoot(int $index): bool
{
if (isset($this->_list[$index])) {
return $this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT;
}
return false;
}
/**
* Gives the total number of PPS's found in the OLE container.
*
* @return int The total number of PPS's found in the OLE container
*/
public function ppsTotal(): int
{
return count($this->_list);
}
/**
* Gets data from a PPS
* If there is no PPS for the index given, it will return an empty string.
*
* @param int $index The index for the PPS
* @param int $position The position from which to start reading
* (relative to the PPS)
* @param int $length The amount of bytes to read (at most)
*
* @return string The binary string containing the data requested
*
* @see OLE_PPS_File::getStream()
*/
public function getData(int $index, int $position, int $length): string
{
// if position is not valid return empty string
if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) {
return '';
}
$fh = $this->getStream($this->_list[$index]);
$data = (string) stream_get_contents($fh, $length, $position);
fclose($fh);
return $data;
}
/**
* Gets the data length from a PPS
* If there is no PPS for the index given, it will return 0.
*
* @param int $index The index for the PPS
*
* @return int The amount of bytes in data the PPS has
*/
public function getDataLength(int $index): int
{
if (isset($this->_list[$index])) {
return $this->_list[$index]->Size;
}
return 0;
}
/**
* Utility function to transform ASCII text to Unicode.
*
* @param string $ascii The ASCII string to transform
*
* @return string The string in Unicode
*/
public static function ascToUcs(string $ascii): string
{
$rawname = '';
$iMax = strlen($ascii);
for ($i = 0; $i < $iMax; ++$i) {
$rawname .= $ascii[$i]
. "\x00";
}
return $rawname;
}
/**
* Utility function
* Returns a string for the OLE container with the date given.
*
* @param float|int $date A timestamp
*
* @return string The string for the OLE container
*/
public static function localDateToOLE($date): string
{
if (!$date) {
return "\x00\x00\x00\x00\x00\x00\x00\x00";
}
$dateTime = Date::dateTimeFromTimestamp("$date");
// days from 1-1-1601 until the beggining of UNIX era
$days = 134774;
// calculate seconds
$big_date = $days * 24 * 3600 + (float) $dateTime->format('U');
// multiply just to make MS happy
$big_date *= 10000000;
// Make HEX string
$res = '';
$factor = 2 ** 56;
while ($factor >= 1) {
$hex = (int) floor($big_date / $factor);
$res = pack('c', $hex) . $res;
$big_date = fmod($big_date, $factor);
$factor /= 256;
}
return $res;
}
/**
* Returns a timestamp from an OLE container's date.
*
* @param string $oleTimestamp A binary string with the encoded date
*
* @return float|int The Unix timestamp corresponding to the string
*/
public static function OLE2LocalDate(string $oleTimestamp)
{
if (strlen($oleTimestamp) != 8) {
throw new ReaderException('Expecting 8 byte string');
}
// convert to units of 100 ns since 1601:
$unpackedTimestamp = unpack('v4', $oleTimestamp) ?: [];
$timestampHigh = (float) $unpackedTimestamp[4] * 65536 + (float) $unpackedTimestamp[3];
$timestampLow = (float) $unpackedTimestamp[2] * 65536 + (float) $unpackedTimestamp[1];
// translate to seconds since 1601:
$timestampHigh /= 10000000;
$timestampLow /= 10000000;
// days from 1601 to 1970:
$days = 134774;
// translate to seconds since 1970:
$unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5);
return IntOrFloat::evaluate($unixTimestamp);
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php 0000644 00000002051 15167673464 0016442 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
class Escher
{
/**
* Drawing Group Container.
*
* @var ?Escher\DggContainer
*/
private ?Escher\DggContainer $dggContainer = null;
/**
* Drawing Container.
*
* @var ?Escher\DgContainer
*/
private ?Escher\DgContainer $dgContainer = null;
/**
* Get Drawing Group Container.
*/
public function getDggContainer(): ?Escher\DggContainer
{
return $this->dggContainer;
}
/**
* Set Drawing Group Container.
*/
public function setDggContainer(Escher\DggContainer $dggContainer): Escher\DggContainer
{
return $this->dggContainer = $dggContainer;
}
/**
* Get Drawing Container.
*/
public function getDgContainer(): ?Escher\DgContainer
{
return $this->dgContainer;
}
/**
* Set Drawing Container.
*/
public function setDgContainer(Escher\DgContainer $dgContainer): Escher\DgContainer
{
return $this->dgContainer = $dgContainer;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php 0000644 00000000612 15167673464 0017253 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
class IntOrFloat
{
/**
* Help some functions with large results operate correctly on 32-bit,
* by returning result as int when possible, float otherwise.
*/
public static function evaluate(float|int $value): float|int
{
$iValue = (int) $value;
return ($value == $iValue) ? $iValue : $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php 0000644 00000046214 15167673464 0016117 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use DateTime;
use DateTimeInterface;
use DateTimeZone;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class Date
{
/** constants */
const CALENDAR_WINDOWS_1900 = 1900; // Base date of 1st Jan 1900 = 1.0
const CALENDAR_MAC_1904 = 1904; // Base date of 2nd Jan 1904 = 1.0
/**
* Names of the months of the year, indexed by shortname
* Planned usage for locale settings.
*
* @var string[]
*/
public static array $monthNames = [
'Jan' => 'January',
'Feb' => 'February',
'Mar' => 'March',
'Apr' => 'April',
'May' => 'May',
'Jun' => 'June',
'Jul' => 'July',
'Aug' => 'August',
'Sep' => 'September',
'Oct' => 'October',
'Nov' => 'November',
'Dec' => 'December',
];
/**
* @var string[]
*/
public static array $numberSuffixes = [
'st',
'nd',
'rd',
'th',
];
/**
* Base calendar year to use for calculations
* Value is either CALENDAR_WINDOWS_1900 (1900) or CALENDAR_MAC_1904 (1904).
*/
protected static int $excelCalendar = self::CALENDAR_WINDOWS_1900;
/**
* Default timezone to use for DateTime objects.
*/
protected static ?DateTimeZone $defaultTimeZone = null;
/**
* Set the Excel calendar (Windows 1900 or Mac 1904).
*
* @param int $baseYear Excel base date (1900 or 1904)
*
* @return bool Success or failure
*/
public static function setExcelCalendar(int $baseYear): bool
{
if (
($baseYear == self::CALENDAR_WINDOWS_1900)
|| ($baseYear == self::CALENDAR_MAC_1904)
) {
self::$excelCalendar = $baseYear;
return true;
}
return false;
}
/**
* Return the Excel calendar (Windows 1900 or Mac 1904).
*
* @return int Excel base date (1900 or 1904)
*/
public static function getExcelCalendar(): int
{
return self::$excelCalendar;
}
/**
* Set the Default timezone to use for dates.
*
* @param null|DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions
*
* @return bool Success or failure
*/
public static function setDefaultTimezone($timeZone): bool
{
try {
$timeZone = self::validateTimeZone($timeZone);
self::$defaultTimeZone = $timeZone;
$retval = true;
} catch (PhpSpreadsheetException) {
$retval = false;
}
return $retval;
}
/**
* Return the Default timezone, or UTC if default not set.
*/
public static function getDefaultTimezone(): DateTimeZone
{
return self::$defaultTimeZone ?? new DateTimeZone('UTC');
}
/**
* Return the Default timezone, or local timezone if default is not set.
*/
public static function getDefaultOrLocalTimezone(): DateTimeZone
{
return self::$defaultTimeZone ?? new DateTimeZone(date_default_timezone_get());
}
/**
* Return the Default timezone even if null.
*/
public static function getDefaultTimezoneOrNull(): ?DateTimeZone
{
return self::$defaultTimeZone;
}
/**
* Validate a timezone.
*
* @param null|DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object
*
* @return ?DateTimeZone The timezone as a timezone object
*/
private static function validateTimeZone($timeZone): ?DateTimeZone
{
if ($timeZone instanceof DateTimeZone || $timeZone === null) {
return $timeZone;
}
if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) {
return new DateTimeZone($timeZone);
}
throw new PhpSpreadsheetException('Invalid timezone');
}
/**
* @param mixed $value Converts a date/time in ISO-8601 standard format date string to an Excel
* serialized timestamp.
* See https://en.wikipedia.org/wiki/ISO_8601 for details of the ISO-8601 standard format.
*/
public static function convertIsoDate(mixed $value): float|int
{
if (!is_string($value)) {
throw new Exception('Non-string value supplied for Iso Date conversion');
}
$date = new DateTime($value);
$dateErrors = DateTime::getLastErrors();
if (is_array($dateErrors) && ($dateErrors['warning_count'] > 0 || $dateErrors['error_count'] > 0)) {
throw new Exception("Invalid string $value supplied for datatype Date");
}
$newValue = SharedDate::PHPToExcel($date);
if ($newValue === false) {
throw new Exception("Invalid string $value supplied for datatype Date");
}
if (preg_match('/^\\s*\\d?\\d:\\d\\d(:\\d\\d([.]\\d+)?)?\\s*(am|pm)?\\s*$/i', $value) == 1) {
$newValue = fmod($newValue, 1.0);
}
return $newValue;
}
/**
* Convert a MS serialized datetime value from Excel to a PHP Date/Time object.
*
* @param float|int $excelTimestamp MS Excel serialized date/time value
* @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
* if you don't want to treat it as a UTC value
* Use the default (UTC) unless you absolutely need a conversion
*
* @return DateTime PHP date/time object
*/
public static function excelToDateTimeObject(float|int $excelTimestamp, null|DateTimeZone|string $timeZone = null): DateTime
{
$timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone);
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) {
if ($excelTimestamp < 1 && self::$excelCalendar === self::CALENDAR_WINDOWS_1900) {
// Unix timestamp base date
$baseDate = new DateTime('1970-01-01', $timeZone);
} else {
// MS Excel calendar base dates
if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
// Allow adjustment for 1900 Leap Year in MS Excel
$baseDate = ($excelTimestamp < 60) ? new DateTime('1899-12-31', $timeZone) : new DateTime('1899-12-30', $timeZone);
} else {
$baseDate = new DateTime('1904-01-01', $timeZone);
}
}
} else {
$baseDate = new DateTime('1899-12-30', $timeZone);
}
$days = floor($excelTimestamp);
$partDay = $excelTimestamp - $days;
$hms = 86400 * $partDay;
$microseconds = (int) round(fmod($hms, 1) * 1000000);
$hms = (int) floor($hms);
$hours = intdiv($hms, 3600);
$hms -= $hours * 3600;
$minutes = intdiv($hms, 60);
$seconds = $hms % 60;
if ($days >= 0) {
$days = '+' . $days;
}
$interval = $days . ' days';
return $baseDate->modify($interval)
->setTime($hours, $minutes, $seconds, $microseconds);
}
/**
* Convert a MS serialized datetime value from Excel to a unix timestamp.
* The use of Unix timestamps, and therefore this function, is discouraged.
* They are not Y2038-safe on a 32-bit system, and have no timezone info.
*
* @param float|int $excelTimestamp MS Excel serialized date/time value
* @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
* if you don't want to treat it as a UTC value
* Use the default (UTC) unless you absolutely need a conversion
*
* @return int Unix timetamp for this date/time
*/
public static function excelToTimestamp($excelTimestamp, $timeZone = null): int
{
$dto = self::excelToDateTimeObject($excelTimestamp, $timeZone);
self::roundMicroseconds($dto);
return (int) $dto->format('U');
}
/**
* Convert a date from PHP to an MS Excel serialized date/time value.
*
* @param mixed $dateValue PHP DateTime object or a string - Unix timestamp is also permitted, but discouraged;
* not Y2038-safe on a 32-bit system, and no timezone info
*
* @return false|float Excel date/time value
* or boolean FALSE on failure
*/
public static function PHPToExcel(mixed $dateValue)
{
if ((is_object($dateValue)) && ($dateValue instanceof DateTimeInterface)) {
return self::dateTimeToExcel($dateValue);
} elseif (is_numeric($dateValue)) {
return self::timestampToExcel($dateValue);
} elseif (is_string($dateValue)) {
return self::stringToExcel($dateValue);
}
return false;
}
/**
* Convert a PHP DateTime object to an MS Excel serialized date/time value.
*
* @param DateTimeInterface $dateValue PHP DateTime object
*
* @return float MS Excel serialized date/time value
*/
public static function dateTimeToExcel(DateTimeInterface $dateValue): float
{
$seconds = (float) sprintf('%d.%06d', $dateValue->format('s'), $dateValue->format('u'));
return self::formattedPHPToExcel(
(int) $dateValue->format('Y'),
(int) $dateValue->format('m'),
(int) $dateValue->format('d'),
(int) $dateValue->format('H'),
(int) $dateValue->format('i'),
$seconds
);
}
/**
* Convert a Unix timestamp to an MS Excel serialized date/time value.
* The use of Unix timestamps, and therefore this function, is discouraged.
* They are not Y2038-safe on a 32-bit system, and have no timezone info.
*
* @param float|int|string $unixTimestamp Unix Timestamp
*
* @return false|float MS Excel serialized date/time value
*/
public static function timestampToExcel($unixTimestamp): bool|float
{
if (!is_numeric($unixTimestamp)) {
return false;
}
return self::dateTimeToExcel(new DateTime('@' . $unixTimestamp));
}
/**
* formattedPHPToExcel.
*
* @return float Excel date/time value
*/
public static function formattedPHPToExcel(int $year, int $month, int $day, int $hours = 0, int $minutes = 0, float|int $seconds = 0): float
{
if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
//
// Fudge factor for the erroneous fact that the year 1900 is treated as a Leap Year in MS Excel
// This affects every date following 28th February 1900
//
$excel1900isLeapYear = true;
if (($year == 1900) && ($month <= 2)) {
$excel1900isLeapYear = false;
}
$myexcelBaseDate = 2415020;
} else {
$myexcelBaseDate = 2416481;
$excel1900isLeapYear = false;
}
// Julian base date Adjustment
if ($month > 2) {
$month -= 3;
} else {
$month += 9;
--$year;
}
// Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0)
$century = (int) substr((string) $year, 0, 2);
$decade = (int) substr((string) $year, 2, 2);
$excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear;
$excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400;
return (float) $excelDate + $excelTime;
}
/**
* Is a given cell a date/time?
*/
public static function isDateTime(Cell $cell, mixed $value = null, bool $dateWithoutTimeOkay = true): bool
{
$result = false;
$worksheet = $cell->getWorksheetOrNull();
$spreadsheet = ($worksheet === null) ? null : $worksheet->getParent();
if ($worksheet !== null && $spreadsheet !== null) {
$index = $spreadsheet->getActiveSheetIndex();
$selected = $worksheet->getSelectedCells();
try {
$result = is_numeric($value ?? $cell->getCalculatedValue())
&& self::isDateTimeFormat(
$worksheet->getStyle(
$cell->getCoordinate()
)->getNumberFormat(),
$dateWithoutTimeOkay
);
} catch (Exception) {
// Result is already false, so no need to actually do anything here
}
$worksheet->setSelectedCells($selected);
$spreadsheet->setActiveSheetIndex($index);
}
return $result;
}
/**
* Is a given NumberFormat code a date/time format code?
*/
public static function isDateTimeFormat(NumberFormat $excelFormatCode, bool $dateWithoutTimeOkay = true): bool
{
return self::isDateTimeFormatCode((string) $excelFormatCode->getFormatCode(), $dateWithoutTimeOkay);
}
private const POSSIBLE_DATETIME_FORMAT_CHARACTERS = 'eymdHs';
private const POSSIBLE_TIME_FORMAT_CHARACTERS = 'Hs'; // note - no 'm' due to ambiguity
/**
* Is a given number format code a date/time?
*/
public static function isDateTimeFormatCode(string $excelFormatCode, bool $dateWithoutTimeOkay = true): bool
{
if (strtolower($excelFormatCode) === strtolower(NumberFormat::FORMAT_GENERAL)) {
// "General" contains an epoch letter 'e', so we trap for it explicitly here (case-insensitive check)
return false;
}
if (preg_match('/[0#]E[+-]0/i', $excelFormatCode)) {
// Scientific format
return false;
}
// Switch on formatcode
$excelFormatCode = (string) NumberFormat::convertSystemFormats($excelFormatCode);
if (in_array($excelFormatCode, NumberFormat::DATE_TIME_OR_DATETIME_ARRAY, true)) {
return $dateWithoutTimeOkay || in_array($excelFormatCode, NumberFormat::TIME_OR_DATETIME_ARRAY);
}
// Typically number, currency or accounting (or occasionally fraction) formats
if ((str_starts_with($excelFormatCode, '_')) || (str_starts_with($excelFormatCode, '0 '))) {
return false;
}
// Some "special formats" provided in German Excel versions were detected as date time value,
// so filter them out here - "\C\H\-00000" (Switzerland) and "\D-00000" (Germany).
if (str_contains($excelFormatCode, '-00000')) {
return false;
}
$possibleFormatCharacters = $dateWithoutTimeOkay ? self::POSSIBLE_DATETIME_FORMAT_CHARACTERS : self::POSSIBLE_TIME_FORMAT_CHARACTERS;
// Try checking for any of the date formatting characters that don't appear within square braces
if (preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $excelFormatCode)) {
// We might also have a format mask containing quoted strings...
// we don't want to test for any of our characters within the quoted blocks
if (str_contains($excelFormatCode, '"')) {
$segMatcher = false;
foreach (explode('"', $excelFormatCode) as $subVal) {
// Only test in alternate array entries (the non-quoted blocks)
$segMatcher = $segMatcher === false;
if (
$segMatcher
&& (preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $subVal))
) {
return true;
}
}
return false;
}
return true;
}
// No date...
return false;
}
/**
* Convert a date/time string to Excel time.
*
* @param string $dateValue Examples: '2009-12-31', '2009-12-31 15:59', '2009-12-31 15:59:10'
*
* @return false|float Excel date/time serial value
*/
public static function stringToExcel(string $dateValue): bool|float
{
if (strlen($dateValue) < 2) {
return false;
}
if (!preg_match('/^(\d{1,4}[ \.\/\-][A-Z]{3,9}([ \.\/\-]\d{1,4})?|[A-Z]{3,9}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?|\d{1,4}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?)( \d{1,2}:\d{1,2}(:\d{1,2})?)?$/iu', $dateValue)) {
return false;
}
$dateValueNew = DateTimeExcel\DateValue::fromString($dateValue);
if (!is_float($dateValueNew)) {
return false;
}
if (str_contains($dateValue, ':')) {
$timeValue = DateTimeExcel\TimeValue::fromString($dateValue);
if (!is_float($timeValue)) {
return false;
}
$dateValueNew += $timeValue;
}
return $dateValueNew;
}
/**
* Converts a month name (either a long or a short name) to a month number.
*
* @param string $monthName Month name or abbreviation
*
* @return int|string Month number (1 - 12), or the original string argument if it isn't a valid month name
*/
public static function monthStringToNumber(string $monthName)
{
$monthIndex = 1;
foreach (self::$monthNames as $shortMonthName => $longMonthName) {
if (($monthName === $longMonthName) || ($monthName === $shortMonthName)) {
return $monthIndex;
}
++$monthIndex;
}
return $monthName;
}
/**
* Strips an ordinal from a numeric value.
*
* @param string $day Day number with an ordinal
*
* @return int|string The integer value with any ordinal stripped, or the original string argument if it isn't a valid numeric
*/
public static function dayStringToNumber(string $day)
{
$strippedDayValue = (str_replace(self::$numberSuffixes, '', $day));
if (is_numeric($strippedDayValue)) {
return (int) $strippedDayValue;
}
return $day;
}
public static function dateTimeFromTimestamp(string $date, ?DateTimeZone $timeZone = null): DateTime
{
$dtobj = DateTime::createFromFormat('U', $date) ?: new DateTime();
$dtobj->setTimeZone($timeZone ?? self::getDefaultOrLocalTimezone());
return $dtobj;
}
public static function formattedDateTimeFromTimestamp(string $date, string $format, ?DateTimeZone $timeZone = null): string
{
$dtobj = self::dateTimeFromTimestamp($date, $timeZone);
return $dtobj->format($format);
}
public static function roundMicroseconds(DateTime $dti): void
{
$microseconds = (int) $dti->format('u');
if ($microseconds >= 500000) {
$dti->modify('+1 second');
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php 0000644 00000023446 15167673464 0016457 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
class OLERead
{
private string $data = '';
// Size of a sector = 512 bytes
const BIG_BLOCK_SIZE = 0x200;
// Size of a short sector = 64 bytes
const SMALL_BLOCK_SIZE = 0x40;
// Size of a directory entry always = 128 bytes
const PROPERTY_STORAGE_BLOCK_SIZE = 0x80;
// Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams
const SMALL_BLOCK_THRESHOLD = 0x1000;
// header offsets
const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2C;
const ROOT_START_BLOCK_POS = 0x30;
const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3C;
const EXTENSION_BLOCK_POS = 0x44;
const NUM_EXTENSION_BLOCK_POS = 0x48;
const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4C;
// property storage offsets (directory offsets)
const SIZE_OF_NAME_POS = 0x40;
const TYPE_POS = 0x42;
const START_BLOCK_POS = 0x74;
const SIZE_POS = 0x78;
public ?int $wrkbook = null;
public ?int $summaryInformation = null;
public ?int $documentSummaryInformation = null;
private int $numBigBlockDepotBlocks;
private int $rootStartBlock;
private int $sbdStartBlock;
private int $extensionBlock;
private int $numExtensionBlocks;
private string $bigBlockChain;
private string $smallBlockChain;
private string $entry;
private int $rootentry;
private array $props = [];
/**
* Read the file.
*/
public function read(string $filename): void
{
File::assertFile($filename);
// Get the file identifier
// Don't bother reading the whole file until we know it's a valid OLE file
$this->data = (string) file_get_contents($filename, false, null, 0, 8);
// Check OLE identifier
$identifierOle = pack('CCCCCCCC', 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1);
if ($this->data != $identifierOle) {
throw new ReaderException('The filename ' . $filename . ' is not recognised as an OLE file');
}
// Get the file data
$this->data = (string) file_get_contents($filename);
// Total number of sectors used for the SAT
$this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
// SecID of the first sector of the directory stream
$this->rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS);
// SecID of the first sector of the SSAT (or -2 if not extant)
$this->sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);
// SecID of the first sector of the MSAT (or -2 if no additional sectors are used)
$this->extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS);
// Total number of sectors used by MSAT
$this->numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS);
$bigBlockDepotBlocks = [];
$pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS;
$bbdBlocks = $this->numBigBlockDepotBlocks;
if ($this->numExtensionBlocks !== 0) {
$bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4;
}
for ($i = 0; $i < $bbdBlocks; ++$i) {
$bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
$pos += 4;
}
for ($j = 0; $j < $this->numExtensionBlocks; ++$j) {
$pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE;
$blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1);
for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) {
$bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
$pos += 4;
}
$bbdBlocks += $blocksToRead;
if ($bbdBlocks < $this->numBigBlockDepotBlocks) {
$this->extensionBlock = self::getInt4d($this->data, $pos);
}
}
$pos = 0;
$this->bigBlockChain = '';
$bbs = self::BIG_BLOCK_SIZE / 4;
for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) {
$pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE;
$this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs);
$pos += 4 * $bbs;
}
$sbdBlock = $this->sbdStartBlock;
$this->smallBlockChain = '';
while ($sbdBlock != -2) {
$pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
$this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs);
$pos += 4 * $bbs;
$sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4);
}
// read the directory stream
$block = $this->rootStartBlock;
$this->entry = $this->readData($block);
$this->readPropertySets();
}
/**
* Extract binary stream data.
*/
public function getStream(?int $stream): ?string
{
if ($stream === null) {
return null;
}
$streamData = '';
if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) {
$rootdata = $this->readData($this->props[$this->rootentry]['startBlock']);
$block = $this->props[$stream]['startBlock'];
while ($block != -2) {
$pos = $block * self::SMALL_BLOCK_SIZE;
$streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE);
$block = self::getInt4d($this->smallBlockChain, $block * 4);
}
return $streamData;
}
$numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE;
if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) {
++$numBlocks;
}
if ($numBlocks == 0) {
return '';
}
$block = $this->props[$stream]['startBlock'];
while ($block != -2) {
$pos = ($block + 1) * self::BIG_BLOCK_SIZE;
$streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
$block = self::getInt4d($this->bigBlockChain, $block * 4);
}
return $streamData;
}
/**
* Read a standard stream (by joining sectors using information from SAT).
*
* @param int $block Sector ID where the stream starts
*
* @return string Data for standard stream
*/
private function readData(int $block): string
{
$data = '';
while ($block != -2) {
$pos = ($block + 1) * self::BIG_BLOCK_SIZE;
$data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
$block = self::getInt4d($this->bigBlockChain, $block * 4);
}
return $data;
}
/**
* Read entries in the directory stream.
*/
private function readPropertySets(): void
{
$offset = 0;
// loop through entires, each entry is 128 bytes
$entryLen = strlen($this->entry);
while ($offset < $entryLen) {
// entry data (128 bytes)
$d = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE);
// size in bytes of name
$nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS + 1]) << 8);
// type of entry
$type = ord($d[self::TYPE_POS]);
// sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook)
// sectorID of first sector of the short-stream container stream, if this entry is root entry
$startBlock = self::getInt4d($d, self::START_BLOCK_POS);
$size = self::getInt4d($d, self::SIZE_POS);
$name = str_replace("\x00", '', substr($d, 0, $nameSize));
$this->props[] = [
'name' => $name,
'type' => $type,
'startBlock' => $startBlock,
'size' => $size,
];
// tmp helper to simplify checks
$upName = strtoupper($name);
// Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook)
if (($upName === 'WORKBOOK') || ($upName === 'BOOK')) {
$this->wrkbook = count($this->props) - 1;
} elseif ($upName === 'ROOT ENTRY' || $upName === 'R') {
// Root entry
$this->rootentry = count($this->props) - 1;
}
// Summary information
if ($name == chr(5) . 'SummaryInformation') {
$this->summaryInformation = count($this->props) - 1;
}
// Additional Document Summary information
if ($name == chr(5) . 'DocumentSummaryInformation') {
$this->documentSummaryInformation = count($this->props) - 1;
}
$offset += self::PROPERTY_STORAGE_BLOCK_SIZE;
}
}
/**
* Read 4 bytes of data at specified position.
*/
private static function getInt4d(string $data, int $pos): int
{
if ($pos < 0) {
// Invalid position
throw new ReaderException('Parameter pos=' . $pos . ' is invalid.');
}
$len = strlen($data);
if ($len < $pos + 4) {
$data .= str_repeat("\0", $pos + 4 - $len);
}
// FIX: represent numbers correctly on 64-bit system
// http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
// Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
$_or_24 = ord($data[$pos + 3]);
if ($_or_24 >= 128) {
// negative number
$_ord_24 = -abs((256 - $_or_24) << 24);
} else {
$_ord_24 = ($_or_24 & 127) << 24;
}
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php 0000644 00000063701 15167673464 0016150 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Font as FontStyle;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
class Font
{
// Methods for resolving autosize value
const AUTOSIZE_METHOD_APPROX = 'approx';
const AUTOSIZE_METHOD_EXACT = 'exact';
private const AUTOSIZE_METHODS = [
self::AUTOSIZE_METHOD_APPROX,
self::AUTOSIZE_METHOD_EXACT,
];
/** Character set codes used by BIFF5-8 in Font records */
const CHARSET_ANSI_LATIN = 0x00;
const CHARSET_SYSTEM_DEFAULT = 0x01;
const CHARSET_SYMBOL = 0x02;
const CHARSET_APPLE_ROMAN = 0x4D;
const CHARSET_ANSI_JAPANESE_SHIFTJIS = 0x80;
const CHARSET_ANSI_KOREAN_HANGUL = 0x81;
const CHARSET_ANSI_KOREAN_JOHAB = 0x82;
const CHARSET_ANSI_CHINESE_SIMIPLIFIED = 0x86; // gb2312
const CHARSET_ANSI_CHINESE_TRADITIONAL = 0x88; // big5
const CHARSET_ANSI_GREEK = 0xA1;
const CHARSET_ANSI_TURKISH = 0xA2;
const CHARSET_ANSI_VIETNAMESE = 0xA3;
const CHARSET_ANSI_HEBREW = 0xB1;
const CHARSET_ANSI_ARABIC = 0xB2;
const CHARSET_ANSI_BALTIC = 0xBA;
const CHARSET_ANSI_CYRILLIC = 0xCC;
const CHARSET_ANSI_THAI = 0xDD;
const CHARSET_ANSI_LATIN_II = 0xEE;
const CHARSET_OEM_LATIN_I = 0xFF;
// XXX: Constants created!
/** Font filenames */
const ARIAL = 'arial.ttf';
const ARIAL_BOLD = 'arialbd.ttf';
const ARIAL_ITALIC = 'ariali.ttf';
const ARIAL_BOLD_ITALIC = 'arialbi.ttf';
const CALIBRI = 'calibri.ttf';
const CALIBRI_BOLD = 'calibrib.ttf';
const CALIBRI_ITALIC = 'calibrii.ttf';
const CALIBRI_BOLD_ITALIC = 'calibriz.ttf';
const COMIC_SANS_MS = 'comic.ttf';
const COMIC_SANS_MS_BOLD = 'comicbd.ttf';
const COURIER_NEW = 'cour.ttf';
const COURIER_NEW_BOLD = 'courbd.ttf';
const COURIER_NEW_ITALIC = 'couri.ttf';
const COURIER_NEW_BOLD_ITALIC = 'courbi.ttf';
const GEORGIA = 'georgia.ttf';
const GEORGIA_BOLD = 'georgiab.ttf';
const GEORGIA_ITALIC = 'georgiai.ttf';
const GEORGIA_BOLD_ITALIC = 'georgiaz.ttf';
const IMPACT = 'impact.ttf';
const LIBERATION_SANS = 'LiberationSans-Regular.ttf';
const LIBERATION_SANS_BOLD = 'LiberationSans-Bold.ttf';
const LIBERATION_SANS_ITALIC = 'LiberationSans-Italic.ttf';
const LIBERATION_SANS_BOLD_ITALIC = 'LiberationSans-BoldItalic.ttf';
const LUCIDA_CONSOLE = 'lucon.ttf';
const LUCIDA_SANS_UNICODE = 'l_10646.ttf';
const MICROSOFT_SANS_SERIF = 'micross.ttf';
const PALATINO_LINOTYPE = 'pala.ttf';
const PALATINO_LINOTYPE_BOLD = 'palab.ttf';
const PALATINO_LINOTYPE_ITALIC = 'palai.ttf';
const PALATINO_LINOTYPE_BOLD_ITALIC = 'palabi.ttf';
const SYMBOL = 'symbol.ttf';
const TAHOMA = 'tahoma.ttf';
const TAHOMA_BOLD = 'tahomabd.ttf';
const TIMES_NEW_ROMAN = 'times.ttf';
const TIMES_NEW_ROMAN_BOLD = 'timesbd.ttf';
const TIMES_NEW_ROMAN_ITALIC = 'timesi.ttf';
const TIMES_NEW_ROMAN_BOLD_ITALIC = 'timesbi.ttf';
const TREBUCHET_MS = 'trebuc.ttf';
const TREBUCHET_MS_BOLD = 'trebucbd.ttf';
const TREBUCHET_MS_ITALIC = 'trebucit.ttf';
const TREBUCHET_MS_BOLD_ITALIC = 'trebucbi.ttf';
const VERDANA = 'verdana.ttf';
const VERDANA_BOLD = 'verdanab.ttf';
const VERDANA_ITALIC = 'verdanai.ttf';
const VERDANA_BOLD_ITALIC = 'verdanaz.ttf';
const FONT_FILE_NAMES = [
'Arial' => [
'x' => self::ARIAL,
'xb' => self::ARIAL_BOLD,
'xi' => self::ARIAL_ITALIC,
'xbi' => self::ARIAL_BOLD_ITALIC,
],
'Calibri' => [
'x' => self::CALIBRI,
'xb' => self::CALIBRI_BOLD,
'xi' => self::CALIBRI_ITALIC,
'xbi' => self::CALIBRI_BOLD_ITALIC,
],
'Comic Sans MS' => [
'x' => self::COMIC_SANS_MS,
'xb' => self::COMIC_SANS_MS_BOLD,
'xi' => self::COMIC_SANS_MS,
'xbi' => self::COMIC_SANS_MS_BOLD,
],
'Courier New' => [
'x' => self::COURIER_NEW,
'xb' => self::COURIER_NEW_BOLD,
'xi' => self::COURIER_NEW_ITALIC,
'xbi' => self::COURIER_NEW_BOLD_ITALIC,
],
'Georgia' => [
'x' => self::GEORGIA,
'xb' => self::GEORGIA_BOLD,
'xi' => self::GEORGIA_ITALIC,
'xbi' => self::GEORGIA_BOLD_ITALIC,
],
'Impact' => [
'x' => self::IMPACT,
'xb' => self::IMPACT,
'xi' => self::IMPACT,
'xbi' => self::IMPACT,
],
'Liberation Sans' => [
'x' => self::LIBERATION_SANS,
'xb' => self::LIBERATION_SANS_BOLD,
'xi' => self::LIBERATION_SANS_ITALIC,
'xbi' => self::LIBERATION_SANS_BOLD_ITALIC,
],
'Lucida Console' => [
'x' => self::LUCIDA_CONSOLE,
'xb' => self::LUCIDA_CONSOLE,
'xi' => self::LUCIDA_CONSOLE,
'xbi' => self::LUCIDA_CONSOLE,
],
'Lucida Sans Unicode' => [
'x' => self::LUCIDA_SANS_UNICODE,
'xb' => self::LUCIDA_SANS_UNICODE,
'xi' => self::LUCIDA_SANS_UNICODE,
'xbi' => self::LUCIDA_SANS_UNICODE,
],
'Microsoft Sans Serif' => [
'x' => self::MICROSOFT_SANS_SERIF,
'xb' => self::MICROSOFT_SANS_SERIF,
'xi' => self::MICROSOFT_SANS_SERIF,
'xbi' => self::MICROSOFT_SANS_SERIF,
],
'Palatino Linotype' => [
'x' => self::PALATINO_LINOTYPE,
'xb' => self::PALATINO_LINOTYPE_BOLD,
'xi' => self::PALATINO_LINOTYPE_ITALIC,
'xbi' => self::PALATINO_LINOTYPE_BOLD_ITALIC,
],
'Symbol' => [
'x' => self::SYMBOL,
'xb' => self::SYMBOL,
'xi' => self::SYMBOL,
'xbi' => self::SYMBOL,
],
'Tahoma' => [
'x' => self::TAHOMA,
'xb' => self::TAHOMA_BOLD,
'xi' => self::TAHOMA,
'xbi' => self::TAHOMA_BOLD,
],
'Times New Roman' => [
'x' => self::TIMES_NEW_ROMAN,
'xb' => self::TIMES_NEW_ROMAN_BOLD,
'xi' => self::TIMES_NEW_ROMAN_ITALIC,
'xbi' => self::TIMES_NEW_ROMAN_BOLD_ITALIC,
],
'Trebuchet MS' => [
'x' => self::TREBUCHET_MS,
'xb' => self::TREBUCHET_MS_BOLD,
'xi' => self::TREBUCHET_MS_ITALIC,
'xbi' => self::TREBUCHET_MS_BOLD_ITALIC,
],
'Verdana' => [
'x' => self::VERDANA,
'xb' => self::VERDANA_BOLD,
'xi' => self::VERDANA_ITALIC,
'xbi' => self::VERDANA_BOLD_ITALIC,
],
];
/**
* Array that can be used to supplement FONT_FILE_NAMES for calculating exact width.
*
* @var array<string, array<string, string>>
*/
private static array $extraFontArray = [];
/** @param array<string, array<string, string>> $extraFontArray */
public static function setExtraFontArray(array $extraFontArray): void
{
self::$extraFontArray = $extraFontArray;
}
/** @return array<string, array<string, string>> */
public static function getExtraFontArray(): array
{
return self::$extraFontArray;
}
/**
* AutoSize method.
*/
private static string $autoSizeMethod = self::AUTOSIZE_METHOD_APPROX;
/**
* Path to folder containing TrueType font .ttf files.
*/
private static string $trueTypeFontPath = '';
/**
* How wide is a default column for a given default font and size?
* Empirical data found by inspecting real Excel files and reading off the pixel width
* in Microsoft Office Excel 2007.
* Added height in points.
*/
public const DEFAULT_COLUMN_WIDTHS = [
'Arial' => [
1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0],
4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25],
9 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.0],
10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75],
],
'Calibri' => [
1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.00],
4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25],
9 => ['px' => 56, 'width' => 9.33203125, 'height' => 12.0],
10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75],
11 => ['px' => 64, 'width' => 9.14062500, 'height' => 15.0],
],
'Verdana' => [
1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0],
4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
8 => ['px' => 64, 'width' => 9.14062500, 'height' => 10.5],
9 => ['px' => 72, 'width' => 9.00000000, 'height' => 11.25],
10 => ['px' => 72, 'width' => 9.00000000, 'height' => 12.75],
],
];
/**
* Set autoSize method.
*
* @param string $method see self::AUTOSIZE_METHOD_*
*
* @return bool Success or failure
*/
public static function setAutoSizeMethod(string $method): bool
{
if (!in_array($method, self::AUTOSIZE_METHODS)) {
return false;
}
self::$autoSizeMethod = $method;
return true;
}
/**
* Get autoSize method.
*/
public static function getAutoSizeMethod(): string
{
return self::$autoSizeMethod;
}
/**
* Set the path to the folder containing .ttf files. There should be a trailing slash.
* Path will be recursively searched for font file.
* Typical locations on various platforms:
* <ul>
* <li>C:/Windows/Fonts/</li>
* <li>/usr/share/fonts/truetype/</li>
* <li>~/.fonts/</li>
* </ul>.
*/
public static function setTrueTypeFontPath(string $folderPath): void
{
self::$trueTypeFontPath = $folderPath;
}
/**
* Get the path to the folder containing .ttf files.
*/
public static function getTrueTypeFontPath(): string
{
return self::$trueTypeFontPath;
}
/**
* Pad amount for exact in pixels; use best guess if null.
*/
private static null|float|int $paddingAmountExact = null;
/**
* Set pad amount for exact in pixels; use best guess if null.
*/
public static function setPaddingAmountExact(null|float|int $paddingAmountExact): void
{
self::$paddingAmountExact = $paddingAmountExact;
}
/**
* Get pad amount for exact in pixels; or null if using best guess.
*/
public static function getPaddingAmountExact(): null|float|int
{
return self::$paddingAmountExact;
}
/**
* Calculate an (approximate) OpenXML column width, based on font size and text contained.
*
* @param FontStyle $font Font object
* @param null|RichText|string $cellText Text to calculate width
* @param int $rotation Rotation angle
* @param null|FontStyle $defaultFont Font object
* @param bool $filterAdjustment Add space for Autofilter or Table dropdown
*/
public static function calculateColumnWidth(
FontStyle $font,
$cellText = '',
int $rotation = 0,
?FontStyle $defaultFont = null,
bool $filterAdjustment = false,
int $indentAdjustment = 0
): float {
// If it is rich text, use plain text
if ($cellText instanceof RichText) {
$cellText = $cellText->getPlainText();
}
// Special case if there are one or more newline characters ("\n")
$cellText = (string) $cellText;
if (str_contains($cellText, "\n")) {
$lineTexts = explode("\n", $cellText);
$lineWidths = [];
foreach ($lineTexts as $lineText) {
$lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont, $filterAdjustment);
}
return max($lineWidths); // width of longest line in cell
}
// Try to get the exact text width in pixels
$approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX;
$columnWidth = 0;
if (!$approximate) {
try {
$columnWidthAdjust = ceil(
self::getTextWidthPixelsExact(
str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
$font,
0
) * 1.07
);
// Width of text in pixels excl. padding
// and addition because Excel adds some padding, just use approx width of 'n' glyph
$columnWidth = self::getTextWidthPixelsExact($cellText, $font, $rotation) + (self::$paddingAmountExact ?? $columnWidthAdjust);
} catch (PhpSpreadsheetException) {
$approximate = true;
}
}
if ($approximate) {
$columnWidthAdjust = self::getTextWidthPixelsApprox(
str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
$font,
0
);
// Width of text in pixels excl. padding, approximation
// and addition because Excel adds some padding, just use approx width of 'n' glyph
$columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
}
// Convert from pixel width to column width
$columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont ?? new FontStyle());
// Return
return round($columnWidth, 4);
}
/**
* Get GD text width in pixels for a string of text in a certain font at a certain rotation angle.
*/
public static function getTextWidthPixelsExact(string $text, FontStyle $font, int $rotation = 0): float
{
// font size should really be supplied in pixels in GD2,
// but since GD2 seems to assume 72dpi, pixels and points are the same
$fontFile = self::getTrueTypeFontFileFromFont($font);
$textBox = imagettfbbox($font->getSize() ?? 10.0, $rotation, $fontFile, $text);
if ($textBox === false) {
// @codeCoverageIgnoreStart
throw new PhpSpreadsheetException('imagettfbbox failed');
// @codeCoverageIgnoreEnd
}
// Get corners positions
$lowerLeftCornerX = $textBox[0];
$lowerRightCornerX = $textBox[2];
$upperRightCornerX = $textBox[4];
$upperLeftCornerX = $textBox[6];
// Consider the rotation when calculating the width
return round(max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX), 4);
}
/**
* Get approximate width in pixels for a string of text in a certain font at a certain rotation angle.
*
* @return int Text width in pixels (no padding added)
*/
public static function getTextWidthPixelsApprox(string $columnText, FontStyle $font, int $rotation = 0): int
{
$fontName = $font->getName();
$fontSize = $font->getSize();
// Calculate column width in pixels.
// We assume fixed glyph width, but count double for "fullwidth" characters.
// Result varies with font name and size.
switch ($fontName) {
case 'Arial':
// value 8 was set because of experience in different exports at Arial 10 font.
$columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
$columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
break;
case 'Verdana':
// value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font.
$columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
$columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
break;
default:
// just assume Calibri
// value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
$columnWidth = (int) (8.26 * StringHelper::countCharactersDbcs($columnText));
$columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
break;
}
// Calculate approximate rotated column width
if ($rotation !== 0) {
if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) {
// stacked text
$columnWidth = 4; // approximation
} else {
// rotated text
$columnWidth = $columnWidth * cos(deg2rad($rotation))
+ $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation
}
}
// pixel width is an integer
return (int) $columnWidth;
}
/**
* Calculate an (approximate) pixel size, based on a font points size.
*
* @param float|int $fontSizeInPoints Font size (in points)
*
* @return int Font size (in pixels)
*/
public static function fontSizeToPixels(float|int $fontSizeInPoints): int
{
return (int) ((4 / 3) * $fontSizeInPoints);
}
/**
* Calculate an (approximate) pixel size, based on inch size.
*
* @param float|int $sizeInInch Font size (in inch)
*
* @return float|int Size (in pixels)
*/
public static function inchSizeToPixels(int|float $sizeInInch): int|float
{
return $sizeInInch * 96;
}
/**
* Calculate an (approximate) pixel size, based on centimeter size.
*
* @param float|int $sizeInCm Font size (in centimeters)
*
* @return float Size (in pixels)
*/
public static function centimeterSizeToPixels(int|float $sizeInCm): float
{
return $sizeInCm * 37.795275591;
}
/**
* Returns the font path given the font.
*
* @return string Path to TrueType font file
*/
public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkPath = true): string
{
if ($checkPath && (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath))) {
throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified');
}
$name = $font->getName();
$fontArray = array_merge(self::FONT_FILE_NAMES, self::$extraFontArray);
if (!isset($fontArray[$name])) {
throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file');
}
$bold = $font->getBold();
$italic = $font->getItalic();
$index = 'x';
if ($bold) {
$index .= 'b';
}
if ($italic) {
$index .= 'i';
}
$fontFile = $fontArray[$name][$index];
$separator = '';
if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') {
$separator = DIRECTORY_SEPARATOR;
}
$fontFileAbsolute = preg_match('~^([A-Za-z]:)?[/\\\\]~', $fontFile) === 1;
if (!$fontFileAbsolute) {
$fontFile = self::findFontFile(self::$trueTypeFontPath, $fontFile) ?? self::$trueTypeFontPath . $separator . $fontFile;
}
// Check if file actually exists
if ($checkPath && !file_exists($fontFile) && !$fontFileAbsolute) {
$alternateName = $name;
if ($index !== 'x' && $fontArray[$name][$index] !== $fontArray[$name]['x']) {
// Bold but no italic:
// Comic Sans
// Tahoma
// Neither bold nor italic:
// Impact
// Lucida Console
// Lucida Sans Unicode
// Microsoft Sans Serif
// Symbol
if ($index === 'xb') {
$alternateName .= ' Bold';
} elseif ($index === 'xi') {
$alternateName .= ' Italic';
} elseif ($fontArray[$name]['xb'] === $fontArray[$name]['xbi']) {
$alternateName .= ' Bold';
} else {
$alternateName .= ' Bold Italic';
}
}
$fontFile = self::$trueTypeFontPath . $separator . $alternateName . '.ttf';
if (!file_exists($fontFile)) {
throw new PhpSpreadsheetException('TrueType Font file not found');
}
}
return $fontFile;
}
public const CHARSET_FROM_FONT_NAME = [
'EucrosiaUPC' => self::CHARSET_ANSI_THAI,
'Wingdings' => self::CHARSET_SYMBOL,
'Wingdings 2' => self::CHARSET_SYMBOL,
'Wingdings 3' => self::CHARSET_SYMBOL,
];
/**
* Returns the associated charset for the font name.
*
* @param string $fontName Font name
*
* @return int Character set code
*/
public static function getCharsetFromFontName(string $fontName): int
{
return self::CHARSET_FROM_FONT_NAME[$fontName] ?? self::CHARSET_ANSI_LATIN;
}
/**
* Get the effective column width for columns without a column dimension or column with width -1
* For example, for Calibri 11 this is 9.140625 (64 px).
*
* @param FontStyle $font The workbooks default font
* @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units
*
* @return ($returnAsPixels is true ? int : float) Column width
*/
public static function getDefaultColumnWidthByFont(FontStyle $font, bool $returnAsPixels = false): float|int
{
if (isset(self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()])) {
// Exact width can be determined
$columnWidth = $returnAsPixels
? self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['px']
: self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['width'];
} else {
// We don't have data for this particular font and size, use approximation by
// extrapolating from Calibri 11
$columnWidth = $returnAsPixels
? self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px']
: self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width'];
$columnWidth = $columnWidth * $font->getSize() / 11;
// Round pixels to closest integer
if ($returnAsPixels) {
$columnWidth = (int) round($columnWidth);
}
}
return $columnWidth;
}
/**
* Get the effective row height for rows without a row dimension or rows with height -1
* For example, for Calibri 11 this is 15 points.
*
* @param FontStyle $font The workbooks default font
*
* @return float Row height in points
*/
public static function getDefaultRowHeightByFont(FontStyle $font): float
{
$name = $font->getName();
$size = $font->getSize();
if (isset(self::DEFAULT_COLUMN_WIDTHS[$name][$size])) {
$rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][$size]['height'];
} elseif ($name === 'Arial' || $name === 'Verdana') {
$rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][10]['height'] * $size / 10.0;
} else {
$rowHeight = self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['height'] * $size / 11.0;
}
return $rowHeight;
}
private static function findFontFile(string $startDirectory, string $desiredFont): ?string
{
$fontPath = null;
if ($startDirectory === '') {
return null;
}
if (file_exists("$startDirectory/$desiredFont")) {
$fontPath = "$startDirectory/$desiredFont";
} else {
$iterations = 0;
$it = new RecursiveDirectoryIterator(
$startDirectory,
RecursiveDirectoryIterator::SKIP_DOTS
| RecursiveDirectoryIterator::FOLLOW_SYMLINKS
);
foreach (
new RecursiveIteratorIterator(
$it,
RecursiveIteratorIterator::LEAVES_ONLY,
RecursiveIteratorIterator::CATCH_GET_CHILD
) as $filex
) {
/** @var string */
$file = $filex;
if (basename($file) === $desiredFont) {
$fontPath = $file;
break;
}
++$iterations;
if ($iterations > 5000) {
// @codeCoverageIgnoreStart
break;
// @codeCoverageIgnoreEnd
}
}
}
return $fontPath;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php 0000644 00000004672 15167673464 0017101 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
class XMLWriter extends \XMLWriter
{
public static bool $debugEnabled = false;
/** Temporary storage method */
const STORAGE_MEMORY = 1;
const STORAGE_DISK = 2;
/**
* Temporary filename.
*/
private string $tempFileName = '';
/**
* Create a new XMLWriter instance.
*
* @param int $temporaryStorage Temporary storage location
* @param ?string $temporaryStorageFolder Temporary storage folder
*/
public function __construct(int $temporaryStorage = self::STORAGE_MEMORY, ?string $temporaryStorageFolder = null)
{
// Open temporary storage
if ($temporaryStorage == self::STORAGE_MEMORY) {
$this->openMemory();
} else {
// Create temporary filename
if ($temporaryStorageFolder === null) {
$temporaryStorageFolder = File::sysGetTempDir();
}
$this->tempFileName = (string) @tempnam($temporaryStorageFolder, 'xml');
// Open storage
if (empty($this->tempFileName) || $this->openUri($this->tempFileName) === false) {
// Fallback to memory...
$this->openMemory();
}
}
// Set default values
if (self::$debugEnabled) {
$this->setIndent(true);
}
}
/**
* Destructor.
*/
public function __destruct()
{
// Unlink temporary files
// There is nothing reasonable to do if unlink fails.
if ($this->tempFileName != '') {
@unlink($this->tempFileName);
}
}
public function __wakeup(): void
{
$this->tempFileName = '';
throw new SpreadsheetException('Unserialize not permitted');
}
/**
* Get written data.
*/
public function getData(): string
{
if ($this->tempFileName == '') {
return $this->outputMemory(true);
}
$this->flush();
return file_get_contents($this->tempFileName) ?: '';
}
/**
* Wrapper method for writeRaw.
*
* @param null|string|string[] $rawTextData
*/
public function writeRawData($rawTextData): bool
{
if (is_array($rawTextData)) {
$rawTextData = implode("\n", $rawTextData);
}
return $this->writeRaw(htmlspecialchars($rawTextData ?? ''));
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php 0000644 00000010356 15167673464 0016633 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use SimpleXMLElement;
class Drawing
{
/**
* Convert pixels to EMU.
*
* @param int $pixelValue Value in pixels
*
* @return float|int Value in EMU
*/
public static function pixelsToEMU(int $pixelValue): int|float
{
return $pixelValue * 9525;
}
/**
* Convert EMU to pixels.
*
* @param int|SimpleXMLElement $emuValue Value in EMU
*
* @return int Value in pixels
*/
public static function EMUToPixels($emuValue): int
{
$emuValue = (int) $emuValue;
if ($emuValue != 0) {
return (int) round($emuValue / 9525);
}
return 0;
}
/**
* Convert pixels to column width. Exact algorithm not known.
* By inspection of a real Excel file using Calibri 11, one finds 1000px ~ 142.85546875
* This gives a conversion factor of 7. Also, we assume that pixels and font size are proportional.
*
* @param int $pixelValue Value in pixels
*
* @return float|int Value in cell dimension
*/
public static function pixelsToCellDimension(int $pixelValue, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont): int|float
{
// Font name and size
$name = $defaultFont->getName();
$size = $defaultFont->getSize();
if (isset(Font::DEFAULT_COLUMN_WIDTHS[$name][$size])) {
// Exact width can be determined
return $pixelValue * Font::DEFAULT_COLUMN_WIDTHS[$name][$size]['width']
/ Font::DEFAULT_COLUMN_WIDTHS[$name][$size]['px'];
}
// We don't have data for this particular font and size, use approximation by
// extrapolating from Calibri 11
return $pixelValue * 11 * Font::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width']
/ Font::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px'] / $size;
}
/**
* Convert column width from (intrinsic) Excel units to pixels.
*
* @param float $cellWidth Value in cell dimension
* @param \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont Default font of the workbook
*
* @return int Value in pixels
*/
public static function cellDimensionToPixels(float $cellWidth, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont): int
{
// Font name and size
$name = $defaultFont->getName();
$size = $defaultFont->getSize();
if (isset(Font::DEFAULT_COLUMN_WIDTHS[$name][$size])) {
// Exact width can be determined
$colWidth = $cellWidth * Font::DEFAULT_COLUMN_WIDTHS[$name][$size]['px']
/ Font::DEFAULT_COLUMN_WIDTHS[$name][$size]['width'];
} else {
// We don't have data for this particular font and size, use approximation by
// extrapolating from Calibri 11
$colWidth = $cellWidth * $size * Font::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px']
/ Font::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width'] / 11;
}
// Round pixels to closest integer
$colWidth = (int) round($colWidth);
return $colWidth;
}
/**
* Convert pixels to points.
*
* @param int $pixelValue Value in pixels
*
* @return float Value in points
*/
public static function pixelsToPoints(int $pixelValue): float
{
return $pixelValue * 0.75;
}
/**
* Convert points to pixels.
*
* @param float|int $pointValue Value in points
*
* @return int Value in pixels
*/
public static function pointsToPixels($pointValue): int
{
if ($pointValue != 0) {
return (int) ceil($pointValue / 0.75);
}
return 0;
}
/**
* Convert degrees to angle.
*
* @param int $degrees Degrees
*
* @return int Angle
*/
public static function degreesToAngle(int $degrees): int
{
return (int) round($degrees * 60000);
}
/**
* Convert angle to degrees.
*
* @param int|SimpleXMLElement $angle Angle
*
* @return int Degrees
*/
public static function angleToDegrees($angle): int
{
$angle = (int) $angle;
if ($angle != 0) {
return (int) round($angle / 60000);
}
return 0;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php 0000644 00000011075 15167673464 0016706 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class CodePage
{
public const DEFAULT_CODE_PAGE = 'CP1252';
private static array $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', 'MAC-CENTRALEUROPE'], // 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)
99999 => ['unsupported'], // Unicode (UTF-8)
];
public static function validate(string $codePage): bool
{
return in_array($codePage, self::$pageArray, true);
}
/**
* Convert Microsoft Code Page Identifier to Code Page Name which iconv
* and mbstring understands.
*
* @param int $codePage Microsoft Code Page Indentifier
*
* @return string Code Page Name
*/
public static function numberToName(int $codePage): string
{
if (array_key_exists($codePage, self::$pageArray)) {
$value = self::$pageArray[$codePage];
if (is_array($value)) {
foreach ($value as $encoding) {
if (@iconv('UTF-8', $encoding, ' ') !== false) {
self::$pageArray[$codePage] = $encoding;
return $encoding;
}
}
throw new PhpSpreadsheetException("Code page $codePage not implemented on this system.");
} else {
return $value;
}
}
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;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php 0000644 00000027054 15167673464 0016011 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Helper\Dimension;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Xls
{
/**
* Get the width of a column in pixels. We use the relationship y = ceil(7x) where
* x is the width in intrinsic Excel units (measuring width in number of normal characters)
* This holds for Arial 10.
*
* @param Worksheet $worksheet The sheet
* @param string $col The column
*
* @return int The width in pixels
*/
public static function sizeCol(Worksheet $worksheet, string $col = 'A'): int
{
// default font of the workbook
$font = $worksheet->getParentOrThrow()->getDefaultStyle()->getFont();
$columnDimensions = $worksheet->getColumnDimensions();
// first find the true column width in pixels (uncollapsed and unhidden)
if (isset($columnDimensions[$col]) && $columnDimensions[$col]->getWidth() != -1) {
// then we have column dimension with explicit width
$columnDimension = $columnDimensions[$col];
$width = $columnDimension->getWidth();
$pixelWidth = Drawing::cellDimensionToPixels($width, $font);
} elseif ($worksheet->getDefaultColumnDimension()->getWidth() != -1) {
// then we have default column dimension with explicit width
$defaultColumnDimension = $worksheet->getDefaultColumnDimension();
$width = $defaultColumnDimension->getWidth();
$pixelWidth = Drawing::cellDimensionToPixels($width, $font);
} else {
// we don't even have any default column dimension. Width depends on default font
$pixelWidth = Font::getDefaultColumnWidthByFont($font, true);
}
// now find the effective column width in pixels
if (isset($columnDimensions[$col]) && !$columnDimensions[$col]->getVisible()) {
$effectivePixelWidth = 0;
} else {
$effectivePixelWidth = $pixelWidth;
}
return $effectivePixelWidth;
}
/**
* Convert the height of a cell from user's units to pixels. By interpolation
* the relationship is: y = 4/3x. If the height hasn't been set by the user we
* use the default value. If the row is hidden we use a value of zero.
*
* @param Worksheet $worksheet The sheet
* @param int $row The row index (1-based)
*
* @return int The width in pixels
*/
public static function sizeRow(Worksheet $worksheet, int $row = 1): int
{
// default font of the workbook
$font = $worksheet->getParentOrThrow()->getDefaultStyle()->getFont();
$rowDimensions = $worksheet->getRowDimensions();
// first find the true row height in pixels (uncollapsed and unhidden)
if (isset($rowDimensions[$row]) && $rowDimensions[$row]->getRowHeight() != -1) {
// then we have a row dimension
$rowDimension = $rowDimensions[$row];
$rowHeight = $rowDimension->getRowHeight();
$pixelRowHeight = (int) ceil(4 * $rowHeight / 3); // here we assume Arial 10
} elseif ($worksheet->getDefaultRowDimension()->getRowHeight() != -1) {
// then we have a default row dimension with explicit height
$defaultRowDimension = $worksheet->getDefaultRowDimension();
$pixelRowHeight = $defaultRowDimension->getRowHeight(Dimension::UOM_PIXELS);
} else {
// we don't even have any default row dimension. Height depends on default font
$pointRowHeight = Font::getDefaultRowHeightByFont($font);
$pixelRowHeight = Font::fontSizeToPixels((int) $pointRowHeight);
}
// now find the effective row height in pixels
if (isset($rowDimensions[$row]) && !$rowDimensions[$row]->getVisible()) {
$effectivePixelRowHeight = 0;
} else {
$effectivePixelRowHeight = $pixelRowHeight;
}
return (int) $effectivePixelRowHeight;
}
/**
* Get the horizontal distance in pixels between two anchors
* The distanceX is found as sum of all the spanning columns widths minus correction for the two offsets.
*
* @param float|int $startOffsetX Offset within start cell measured in 1/1024 of the cell width
* @param float|int $endOffsetX Offset within end cell measured in 1/1024 of the cell width
*
* @return int Horizontal measured in pixels
*/
public static function getDistanceX(Worksheet $worksheet, string $startColumn = 'A', float|int $startOffsetX = 0, string $endColumn = 'A', float|int $endOffsetX = 0): int
{
$distanceX = 0;
// add the widths of the spanning columns
$startColumnIndex = Coordinate::columnIndexFromString($startColumn);
$endColumnIndex = Coordinate::columnIndexFromString($endColumn);
for ($i = $startColumnIndex; $i <= $endColumnIndex; ++$i) {
$distanceX += self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($i));
}
// correct for offsetX in startcell
$distanceX -= (int) floor(self::sizeCol($worksheet, $startColumn) * $startOffsetX / 1024);
// correct for offsetX in endcell
$distanceX -= (int) floor(self::sizeCol($worksheet, $endColumn) * (1 - $endOffsetX / 1024));
return $distanceX;
}
/**
* Get the vertical distance in pixels between two anchors
* The distanceY is found as sum of all the spanning rows minus two offsets.
*
* @param int $startRow (1-based)
* @param float|int $startOffsetY Offset within start cell measured in 1/256 of the cell height
* @param int $endRow (1-based)
* @param float|int $endOffsetY Offset within end cell measured in 1/256 of the cell height
*
* @return int Vertical distance measured in pixels
*/
public static function getDistanceY(Worksheet $worksheet, int $startRow = 1, float|int $startOffsetY = 0, int $endRow = 1, float|int $endOffsetY = 0): int
{
$distanceY = 0;
// add the widths of the spanning rows
for ($row = $startRow; $row <= $endRow; ++$row) {
$distanceY += self::sizeRow($worksheet, $row);
}
// correct for offsetX in startcell
$distanceY -= (int) floor(self::sizeRow($worksheet, $startRow) * $startOffsetY / 256);
// correct for offsetX in endcell
$distanceY -= (int) floor(self::sizeRow($worksheet, $endRow) * (1 - $endOffsetY / 256));
return $distanceY;
}
/**
* Convert 1-cell anchor coordinates to 2-cell anchor coordinates
* This function is ported from PEAR Spreadsheet_Writer_Excel with small modifications.
*
* Calculate the vertices that define the position of the image as required by
* the OBJ record.
*
* +------------+------------+
* | A | B |
* +-----+------------+------------+
* | |(x1,y1) | |
* | 1 |(A1)._______|______ |
* | | | | |
* | | | | |
* +-----+----| BITMAP |-----+
* | | | | |
* | 2 | |______________. |
* | | | (B2)|
* | | | (x2,y2)|
* +---- +------------+------------+
*
* Example of a bitmap that covers some of the area from cell A1 to cell B2.
*
* Based on the width and height of the bitmap we need to calculate 8 vars:
* $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
* The width and height of the cells are also variable and have to be taken into
* account.
* The values of $col_start and $row_start are passed in from the calling
* function. The values of $col_end and $row_end are calculated by subtracting
* the width and height of the bitmap from the width and height of the
* underlying cells.
* The vertices are expressed as a percentage of the underlying cell width as
* follows (rhs values are in pixels):
*
* x1 = X / W *1024
* y1 = Y / H *256
* x2 = (X-1) / W *1024
* y2 = (Y-1) / H *256
*
* Where: X is distance from the left side of the underlying cell
* Y is distance from the top of the underlying cell
* W is the width of the cell
* H is the height of the cell
*
* @param string $coordinates E.g. 'A1'
* @param int $offsetX Horizontal offset in pixels
* @param int $offsetY Vertical offset in pixels
* @param int $width Width in pixels
* @param int $height Height in pixels
*/
public static function oneAnchor2twoAnchor(Worksheet $worksheet, string $coordinates, int $offsetX, int $offsetY, int $width, int $height): ?array
{
[$col_start, $row] = Coordinate::indexesFromString($coordinates);
$row_start = $row - 1;
$x1 = $offsetX;
$y1 = $offsetY;
// Initialise end cell to the same as the start cell
$col_end = $col_start; // Col containing lower right corner of object
$row_end = $row_start; // Row containing bottom right corner of object
// Zero the specified offset if greater than the cell dimensions
if ($x1 >= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start))) {
$x1 = 0;
}
if ($y1 >= self::sizeRow($worksheet, $row_start + 1)) {
$y1 = 0;
}
$width = $width + $x1 - 1;
$height = $height + $y1 - 1;
// Subtract the underlying cell widths to find the end cell of the image
while ($width >= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end))) {
$width -= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end));
++$col_end;
}
// Subtract the underlying cell heights to find the end cell of the image
while ($height >= self::sizeRow($worksheet, $row_end + 1)) {
$height -= self::sizeRow($worksheet, $row_end + 1);
++$row_end;
}
// Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
// with zero height or width.
if (self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start)) == 0) {
return null;
}
if (self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)) == 0) {
return null;
}
if (self::sizeRow($worksheet, $row_start + 1) == 0) {
return null;
}
if (self::sizeRow($worksheet, $row_end + 1) == 0) {
return null;
}
// Convert the pixel values to the percentage value expected by Excel
$x1 = $x1 / self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start)) * 1024;
$y1 = $y1 / self::sizeRow($worksheet, $row_start + 1) * 256;
$x2 = ($width + 1) / self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)) * 1024; // Distance to right side of object
$y2 = ($height + 1) / self::sizeRow($worksheet, $row_end + 1) * 256; // Distance to bottom of object
$startCoordinates = Coordinate::stringFromColumnIndex($col_start) . ($row_start + 1);
$endCoordinates = Coordinate::stringFromColumnIndex($col_end) . ($row_end + 1);
return [
'startCoordinates' => $startCoordinates,
'startOffsetX' => $x1,
'startOffsetY' => $y1,
'endCoordinates' => $endCoordinates,
'endOffsetX' => $x2,
'endOffsetY' => $y2,
];
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php 0000644 00000004200 15167673464 0016761 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use DateTimeZone;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class TimeZone
{
/**
* Default Timezone used for date/time conversions.
*/
protected static string $timezone = 'UTC';
/**
* Validate a Timezone name.
*
* @param string $timezoneName Time zone (e.g. 'Europe/London')
*
* @return bool Success or failure
*/
private static function validateTimeZone(string $timezoneName): bool
{
return in_array($timezoneName, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC), true);
}
/**
* Set the Default Timezone used for date/time conversions.
*
* @param string $timezoneName Time zone (e.g. 'Europe/London')
*
* @return bool Success or failure
*/
public static function setTimeZone(string $timezoneName): bool
{
if (self::validateTimeZone($timezoneName)) {
self::$timezone = $timezoneName;
return true;
}
return false;
}
/**
* Return the Default Timezone used for date/time conversions.
*
* @return string Timezone (e.g. 'Europe/London')
*/
public static function getTimeZone(): string
{
return self::$timezone;
}
/**
* Return the Timezone offset used for date/time conversions to/from UST
* This requires both the timezone and the calculated date/time to allow for local DST.
*
* @param ?string $timezoneName The timezone for finding the adjustment to UST
* @param float|int $timestamp PHP date/time value
*
* @return int Number of seconds for timezone adjustment
*/
public static function getTimeZoneAdjustment(?string $timezoneName, $timestamp): int
{
$timezoneName = $timezoneName ?? self::$timezone;
$dtobj = Date::dateTimeFromTimestamp("$timestamp");
if (!self::validateTimeZone($timezoneName)) {
throw new PhpSpreadsheetException("Invalid timezone $timezoneName");
}
$dtobj->setTimeZone(new DateTimeZone($timezoneName));
return $dtobj->getOffset();
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/File.php 0000644 00000013740 15167673464 0016117 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use ZipArchive;
class File
{
/**
* Use Temp or File Upload Temp for temporary files.
*/
protected static bool $useUploadTempDirectory = false;
/**
* Set the flag indicating whether the File Upload Temp directory should be used for temporary files.
*/
public static function setUseUploadTempDirectory(bool $useUploadTempDir): void
{
self::$useUploadTempDirectory = (bool) $useUploadTempDir;
}
/**
* Get the flag indicating whether the File Upload Temp directory should be used for temporary files.
*/
public static function getUseUploadTempDirectory(): bool
{
return self::$useUploadTempDirectory;
}
// https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
// Section 4.3.7
// Looks like there might be endian-ness considerations
private const ZIP_FIRST_4 = [
"\x50\x4b\x03\x04", // what it looks like on my system
"\x04\x03\x4b\x50", // what it says in documentation
];
private static function validateZipFirst4(string $zipFile): bool
{
$contents = @file_get_contents($zipFile, false, null, 0, 4);
return in_array($contents, self::ZIP_FIRST_4, true);
}
/**
* Verify if a file exists.
*/
public static function fileExists(string $filename): bool
{
// Sick construction, but it seems that
// file_exists returns strange values when
// doing the original file_exists on ZIP archives...
if (strtolower(substr($filename, 0, 6)) == 'zip://') {
// Open ZIP file and verify if the file exists
$zipFile = substr($filename, 6, strrpos($filename, '#') - 6);
$archiveFile = substr($filename, strrpos($filename, '#') + 1);
if (self::validateZipFirst4($zipFile)) {
$zip = new ZipArchive();
$res = $zip->open($zipFile);
if ($res === true) {
$returnValue = ($zip->getFromName($archiveFile) !== false);
$zip->close();
return $returnValue;
}
}
return false;
}
return file_exists($filename);
}
/**
* Returns canonicalized absolute pathname, also for ZIP archives.
*/
public static function realpath(string $filename): string
{
// Returnvalue
$returnValue = '';
// Try using realpath()
if (file_exists($filename)) {
$returnValue = realpath($filename) ?: '';
}
// Found something?
if ($returnValue === '') {
$pathArray = explode('/', $filename);
while (in_array('..', $pathArray) && $pathArray[0] != '..') {
$iMax = count($pathArray);
for ($i = 1; $i < $iMax; ++$i) {
if ($pathArray[$i] == '..') {
array_splice($pathArray, $i - 1, 2);
break;
}
}
}
$returnValue = implode('/', $pathArray);
}
// Return
return $returnValue;
}
/**
* Get the systems temporary directory.
*/
public static function sysGetTempDir(): string
{
$path = sys_get_temp_dir();
if (self::$useUploadTempDirectory) {
// use upload-directory when defined to allow running on environments having very restricted
// open_basedir configs
if (ini_get('upload_tmp_dir') !== false) {
if ($temp = ini_get('upload_tmp_dir')) {
if (file_exists($temp)) {
$path = $temp;
}
}
}
}
return realpath($path) ?: '';
}
public static function temporaryFilename(): string
{
$filename = tempnam(self::sysGetTempDir(), 'phpspreadsheet');
if ($filename === false) {
throw new Exception('Could not create temporary file');
}
return $filename;
}
/**
* Assert that given path is an existing file and is readable, otherwise throw exception.
*/
public static function assertFile(string $filename, string $zipMember = ''): void
{
if (!is_file($filename)) {
throw new ReaderException('File "' . $filename . '" does not exist.');
}
if (!is_readable($filename)) {
throw new ReaderException('Could not open "' . $filename . '" for reading.');
}
if ($zipMember !== '') {
$zipfile = "zip://$filename#$zipMember";
if (!self::fileExists($zipfile)) {
// Has the file been saved with Windoze directory separators rather than unix?
$zipfile = "zip://$filename#" . str_replace('/', '\\', $zipMember);
if (!self::fileExists($zipfile)) {
throw new ReaderException("Could not find zip member $zipfile");
}
}
}
}
/**
* Same as assertFile, except return true/false and don't throw Exception.
*/
public static function testFileNoThrow(string $filename, ?string $zipMember = null): bool
{
if (!is_file($filename)) {
return false;
}
if (!is_readable($filename)) {
return false;
}
if ($zipMember === null) {
return true;
}
// validate zip, but don't check specific member
if ($zipMember === '') {
return self::validateZipFirst4($filename);
}
$zipfile = "zip://$filename#$zipMember";
if (self::fileExists($zipfile)) {
return true;
}
// Has the file been saved with Windoze directory separators rather than unix?
$zipfile = "zip://$filename#" . str_replace('/', '\\', $zipMember);
return self::fileExists($zipfile);
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php 0000644 00000055272 15167673464 0017654 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
class StringHelper
{
/**
* Control characters array.
*
* @var string[]
*/
private static array $controlCharacters = [];
/**
* SYLK Characters array.
*/
private static array $SYLKCharacters = [];
/**
* Decimal separator.
*/
private static ?string $decimalSeparator;
/**
* Thousands separator.
*/
private static ?string $thousandsSeparator;
/**
* Currency code.
*/
private static ?string $currencyCode;
/**
* Is iconv extension avalable?
*/
private static ?bool $isIconvEnabled;
/**
* iconv options.
*/
private static string $iconvOptions = '//IGNORE//TRANSLIT';
/**
* Build control characters array.
*/
private static function buildControlCharacters(): void
{
for ($i = 0; $i <= 31; ++$i) {
if ($i != 9 && $i != 10 && $i != 13) {
$find = '_x' . sprintf('%04s', strtoupper(dechex($i))) . '_';
$replace = chr($i);
self::$controlCharacters[$find] = $replace;
}
}
}
/**
* Build SYLK characters array.
*/
private static function buildSYLKCharacters(): void
{
self::$SYLKCharacters = [
"\x1B 0" => chr(0),
"\x1B 1" => chr(1),
"\x1B 2" => chr(2),
"\x1B 3" => chr(3),
"\x1B 4" => chr(4),
"\x1B 5" => chr(5),
"\x1B 6" => chr(6),
"\x1B 7" => chr(7),
"\x1B 8" => chr(8),
"\x1B 9" => chr(9),
"\x1B :" => chr(10),
"\x1B ;" => chr(11),
"\x1B <" => chr(12),
"\x1B =" => chr(13),
"\x1B >" => chr(14),
"\x1B ?" => chr(15),
"\x1B!0" => chr(16),
"\x1B!1" => chr(17),
"\x1B!2" => chr(18),
"\x1B!3" => chr(19),
"\x1B!4" => chr(20),
"\x1B!5" => chr(21),
"\x1B!6" => chr(22),
"\x1B!7" => chr(23),
"\x1B!8" => chr(24),
"\x1B!9" => chr(25),
"\x1B!:" => chr(26),
"\x1B!;" => chr(27),
"\x1B!<" => chr(28),
"\x1B!=" => chr(29),
"\x1B!>" => chr(30),
"\x1B!?" => chr(31),
"\x1B'?" => chr(127),
"\x1B(0" => '€', // 128 in CP1252
"\x1B(2" => '‚', // 130 in CP1252
"\x1B(3" => 'ƒ', // 131 in CP1252
"\x1B(4" => '„', // 132 in CP1252
"\x1B(5" => '…', // 133 in CP1252
"\x1B(6" => '†', // 134 in CP1252
"\x1B(7" => '‡', // 135 in CP1252
"\x1B(8" => 'ˆ', // 136 in CP1252
"\x1B(9" => '‰', // 137 in CP1252
"\x1B(:" => 'Š', // 138 in CP1252
"\x1B(;" => '‹', // 139 in CP1252
"\x1BNj" => 'Œ', // 140 in CP1252
"\x1B(>" => 'Ž', // 142 in CP1252
"\x1B)1" => '‘', // 145 in CP1252
"\x1B)2" => '’', // 146 in CP1252
"\x1B)3" => '“', // 147 in CP1252
"\x1B)4" => '”', // 148 in CP1252
"\x1B)5" => '•', // 149 in CP1252
"\x1B)6" => '–', // 150 in CP1252
"\x1B)7" => '—', // 151 in CP1252
"\x1B)8" => '˜', // 152 in CP1252
"\x1B)9" => '™', // 153 in CP1252
"\x1B):" => 'š', // 154 in CP1252
"\x1B);" => '›', // 155 in CP1252
"\x1BNz" => 'œ', // 156 in CP1252
"\x1B)>" => 'ž', // 158 in CP1252
"\x1B)?" => 'Ÿ', // 159 in CP1252
"\x1B*0" => ' ', // 160 in CP1252
"\x1BN!" => '¡', // 161 in CP1252
"\x1BN\"" => '¢', // 162 in CP1252
"\x1BN#" => '£', // 163 in CP1252
"\x1BN(" => '¤', // 164 in CP1252
"\x1BN%" => '¥', // 165 in CP1252
"\x1B*6" => '¦', // 166 in CP1252
"\x1BN'" => '§', // 167 in CP1252
"\x1BNH " => '¨', // 168 in CP1252
"\x1BNS" => '©', // 169 in CP1252
"\x1BNc" => 'ª', // 170 in CP1252
"\x1BN+" => '«', // 171 in CP1252
"\x1B*<" => '¬', // 172 in CP1252
"\x1B*=" => '', // 173 in CP1252
"\x1BNR" => '®', // 174 in CP1252
"\x1B*?" => '¯', // 175 in CP1252
"\x1BN0" => '°', // 176 in CP1252
"\x1BN1" => '±', // 177 in CP1252
"\x1BN2" => '²', // 178 in CP1252
"\x1BN3" => '³', // 179 in CP1252
"\x1BNB " => '´', // 180 in CP1252
"\x1BN5" => 'µ', // 181 in CP1252
"\x1BN6" => '¶', // 182 in CP1252
"\x1BN7" => '·', // 183 in CP1252
"\x1B+8" => '¸', // 184 in CP1252
"\x1BNQ" => '¹', // 185 in CP1252
"\x1BNk" => 'º', // 186 in CP1252
"\x1BN;" => '»', // 187 in CP1252
"\x1BN<" => '¼', // 188 in CP1252
"\x1BN=" => '½', // 189 in CP1252
"\x1BN>" => '¾', // 190 in CP1252
"\x1BN?" => '¿', // 191 in CP1252
"\x1BNAA" => 'À', // 192 in CP1252
"\x1BNBA" => 'Á', // 193 in CP1252
"\x1BNCA" => 'Â', // 194 in CP1252
"\x1BNDA" => 'Ã', // 195 in CP1252
"\x1BNHA" => 'Ä', // 196 in CP1252
"\x1BNJA" => 'Å', // 197 in CP1252
"\x1BNa" => 'Æ', // 198 in CP1252
"\x1BNKC" => 'Ç', // 199 in CP1252
"\x1BNAE" => 'È', // 200 in CP1252
"\x1BNBE" => 'É', // 201 in CP1252
"\x1BNCE" => 'Ê', // 202 in CP1252
"\x1BNHE" => 'Ë', // 203 in CP1252
"\x1BNAI" => 'Ì', // 204 in CP1252
"\x1BNBI" => 'Í', // 205 in CP1252
"\x1BNCI" => 'Î', // 206 in CP1252
"\x1BNHI" => 'Ï', // 207 in CP1252
"\x1BNb" => 'Ð', // 208 in CP1252
"\x1BNDN" => 'Ñ', // 209 in CP1252
"\x1BNAO" => 'Ò', // 210 in CP1252
"\x1BNBO" => 'Ó', // 211 in CP1252
"\x1BNCO" => 'Ô', // 212 in CP1252
"\x1BNDO" => 'Õ', // 213 in CP1252
"\x1BNHO" => 'Ö', // 214 in CP1252
"\x1B-7" => '×', // 215 in CP1252
"\x1BNi" => 'Ø', // 216 in CP1252
"\x1BNAU" => 'Ù', // 217 in CP1252
"\x1BNBU" => 'Ú', // 218 in CP1252
"\x1BNCU" => 'Û', // 219 in CP1252
"\x1BNHU" => 'Ü', // 220 in CP1252
"\x1B-=" => 'Ý', // 221 in CP1252
"\x1BNl" => 'Þ', // 222 in CP1252
"\x1BN{" => 'ß', // 223 in CP1252
"\x1BNAa" => 'à', // 224 in CP1252
"\x1BNBa" => 'á', // 225 in CP1252
"\x1BNCa" => 'â', // 226 in CP1252
"\x1BNDa" => 'ã', // 227 in CP1252
"\x1BNHa" => 'ä', // 228 in CP1252
"\x1BNJa" => 'å', // 229 in CP1252
"\x1BNq" => 'æ', // 230 in CP1252
"\x1BNKc" => 'ç', // 231 in CP1252
"\x1BNAe" => 'è', // 232 in CP1252
"\x1BNBe" => 'é', // 233 in CP1252
"\x1BNCe" => 'ê', // 234 in CP1252
"\x1BNHe" => 'ë', // 235 in CP1252
"\x1BNAi" => 'ì', // 236 in CP1252
"\x1BNBi" => 'í', // 237 in CP1252
"\x1BNCi" => 'î', // 238 in CP1252
"\x1BNHi" => 'ï', // 239 in CP1252
"\x1BNs" => 'ð', // 240 in CP1252
"\x1BNDn" => 'ñ', // 241 in CP1252
"\x1BNAo" => 'ò', // 242 in CP1252
"\x1BNBo" => 'ó', // 243 in CP1252
"\x1BNCo" => 'ô', // 244 in CP1252
"\x1BNDo" => 'õ', // 245 in CP1252
"\x1BNHo" => 'ö', // 246 in CP1252
"\x1B/7" => '÷', // 247 in CP1252
"\x1BNy" => 'ø', // 248 in CP1252
"\x1BNAu" => 'ù', // 249 in CP1252
"\x1BNBu" => 'ú', // 250 in CP1252
"\x1BNCu" => 'û', // 251 in CP1252
"\x1BNHu" => 'ü', // 252 in CP1252
"\x1B/=" => 'ý', // 253 in CP1252
"\x1BN|" => 'þ', // 254 in CP1252
"\x1BNHy" => 'ÿ', // 255 in CP1252
];
}
/**
* Get whether iconv extension is available.
*/
public static function getIsIconvEnabled(): bool
{
if (isset(self::$isIconvEnabled)) {
return self::$isIconvEnabled;
}
// Assume no problems with iconv
self::$isIconvEnabled = true;
// Fail if iconv doesn't exist
if (!function_exists('iconv')) {
self::$isIconvEnabled = false;
} elseif (!@iconv('UTF-8', 'UTF-16LE', 'x')) {
// Sometimes iconv is not working, and e.g. iconv('UTF-8', 'UTF-16LE', 'x') just returns false,
self::$isIconvEnabled = false;
} elseif (defined('PHP_OS') && @stristr(PHP_OS, 'AIX') && defined('ICONV_IMPL') && (@strcasecmp(ICONV_IMPL, 'unknown') == 0) && defined('ICONV_VERSION') && (@strcasecmp(ICONV_VERSION, 'unknown') == 0)) {
// CUSTOM: IBM AIX iconv() does not work
self::$isIconvEnabled = false;
}
// Deactivate iconv default options if they fail (as seen on IMB i)
if (self::$isIconvEnabled && !@iconv('UTF-8', 'UTF-16LE' . self::$iconvOptions, 'x')) {
self::$iconvOptions = '';
}
return self::$isIconvEnabled;
}
private static function buildCharacterSets(): void
{
if (empty(self::$controlCharacters)) {
self::buildControlCharacters();
}
if (empty(self::$SYLKCharacters)) {
self::buildSYLKCharacters();
}
}
/**
* Convert from OpenXML escaped control character to PHP control character.
*
* Excel 2007 team:
* ----------------
* That's correct, control characters are stored directly in the shared-strings table.
* We do encode characters that cannot be represented in XML using the following escape sequence:
* _xHHHH_ where H represents a hexadecimal character in the character's value...
* So you could end up with something like _x0008_ in a string (either in a cell value (<v>)
* element or in the shared string <t> element.
*
* @param string $textValue Value to unescape
*/
public static function controlCharacterOOXML2PHP(string $textValue): string
{
self::buildCharacterSets();
return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $textValue);
}
/**
* Convert from PHP control character to OpenXML escaped control character.
*
* Excel 2007 team:
* ----------------
* That's correct, control characters are stored directly in the shared-strings table.
* We do encode characters that cannot be represented in XML using the following escape sequence:
* _xHHHH_ where H represents a hexadecimal character in the character's value...
* So you could end up with something like _x0008_ in a string (either in a cell value (<v>)
* element or in the shared string <t> element.
*
* @param string $textValue Value to escape
*/
public static function controlCharacterPHP2OOXML(string $textValue): string
{
self::buildCharacterSets();
return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $textValue);
}
/**
* Try to sanitize UTF8, replacing invalid sequences with Unicode substitution characters.
*/
public static function sanitizeUTF8(string $textValue): string
{
$textValue = str_replace(["\xef\xbf\xbe", "\xef\xbf\xbf"], "\xef\xbf\xbd", $textValue);
$subst = mb_substitute_character(); // default is question mark
mb_substitute_character(65533); // Unicode substitution character
// Phpstan does not think this can return false.
$returnValue = mb_convert_encoding($textValue, 'UTF-8', 'UTF-8');
mb_substitute_character($subst);
return $returnValue;
}
/**
* Check if a string contains UTF8 data.
*/
public static function isUTF8(string $textValue): bool
{
return $textValue === self::sanitizeUTF8($textValue);
}
/**
* Formats a numeric value as a string for output in various output writers forcing
* point as decimal separator in case locale is other than English.
*/
public static function formatNumber(float|int|string|null $numericValue): string
{
if (is_float($numericValue)) {
return str_replace(',', '.', (string) $numericValue);
}
return (string) $numericValue;
}
/**
* Converts a UTF-8 string into BIFF8 Unicode string data (8-bit string length)
* Writes the string using uncompressed notation, no rich text, no Asian phonetics
* If mbstring extension is not available, ASCII is assumed, and compressed notation is used
* although this will give wrong results for non-ASCII strings
* see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
*
* @param string $textValue UTF-8 encoded string
* @param array<int, array{strlen: int, fontidx: int}> $arrcRuns Details of rich text runs in $value
*/
public static function UTF8toBIFF8UnicodeShort(string $textValue, array $arrcRuns = []): string
{
// character count
$ln = self::countCharacters($textValue, 'UTF-8');
// option flags
if (empty($arrcRuns)) {
$data = pack('CC', $ln, 0x0001);
// characters
$data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
} else {
$data = pack('vC', $ln, 0x09);
$data .= pack('v', count($arrcRuns));
// characters
$data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
foreach ($arrcRuns as $cRun) {
$data .= pack('v', $cRun['strlen']);
$data .= pack('v', $cRun['fontidx']);
}
}
return $data;
}
/**
* Converts a UTF-8 string into BIFF8 Unicode string data (16-bit string length)
* Writes the string using uncompressed notation, no rich text, no Asian phonetics
* If mbstring extension is not available, ASCII is assumed, and compressed notation is used
* although this will give wrong results for non-ASCII strings
* see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
*
* @param string $textValue UTF-8 encoded string
*/
public static function UTF8toBIFF8UnicodeLong(string $textValue): string
{
// characters
$chars = self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
$ln = (int) (strlen($chars) / 2); // N.B. - strlen, not mb_strlen issue #642
return pack('vC', $ln, 0x0001) . $chars;
}
/**
* Convert string from one encoding to another.
*
* @param string $to Encoding to convert to, e.g. 'UTF-8'
* @param string $from Encoding to convert from, e.g. 'UTF-16LE'
*/
public static function convertEncoding(string $textValue, string $to, string $from): string
{
if (self::getIsIconvEnabled()) {
$result = iconv($from, $to . self::$iconvOptions, $textValue);
if (false !== $result) {
return $result;
}
}
return mb_convert_encoding($textValue, $to, $from);
}
/**
* Get character count.
*
* @param string $encoding Encoding
*
* @return int Character count
*/
public static function countCharacters(string $textValue, string $encoding = 'UTF-8'): int
{
return mb_strlen($textValue, $encoding);
}
/**
* Get character count using mb_strwidth rather than mb_strlen.
*
* @param string $encoding Encoding
*
* @return int Character count
*/
public static function countCharactersDbcs(string $textValue, string $encoding = 'UTF-8'): int
{
return mb_strwidth($textValue, $encoding);
}
/**
* Get a substring of a UTF-8 encoded string.
*
* @param string $textValue UTF-8 encoded string
* @param int $offset Start offset
* @param ?int $length Maximum number of characters in substring
*/
public static function substring(string $textValue, int $offset, ?int $length = 0): string
{
return mb_substr($textValue, $offset, $length, 'UTF-8');
}
/**
* Convert a UTF-8 encoded string to upper case.
*
* @param string $textValue UTF-8 encoded string
*/
public static function strToUpper(string $textValue): string
{
return mb_convert_case($textValue, MB_CASE_UPPER, 'UTF-8');
}
/**
* Convert a UTF-8 encoded string to lower case.
*
* @param string $textValue UTF-8 encoded string
*/
public static function strToLower(string $textValue): string
{
return mb_convert_case($textValue, MB_CASE_LOWER, 'UTF-8');
}
/**
* Convert a UTF-8 encoded string to title/proper case
* (uppercase every first character in each word, lower case all other characters).
*
* @param string $textValue UTF-8 encoded string
*/
public static function strToTitle(string $textValue): string
{
return mb_convert_case($textValue, MB_CASE_TITLE, 'UTF-8');
}
public static function mbIsUpper(string $character): bool
{
return mb_strtolower($character, 'UTF-8') !== $character;
}
/**
* Splits a UTF-8 string into an array of individual characters.
*/
public static function mbStrSplit(string $string): array
{
// Split at all position not after the start: ^
// and not before the end: $
$split = preg_split('/(?<!^)(?!$)/u', $string);
return ($split === false) ? [] : $split;
}
/**
* Reverse the case of a string, so that all uppercase characters become lowercase
* and all lowercase characters become uppercase.
*
* @param string $textValue UTF-8 encoded string
*/
public static function strCaseReverse(string $textValue): string
{
$characters = self::mbStrSplit($textValue);
foreach ($characters as &$character) {
if (self::mbIsUpper($character)) {
$character = mb_strtolower($character, 'UTF-8');
} else {
$character = mb_strtoupper($character, 'UTF-8');
}
}
return implode('', $characters);
}
/**
* Get the decimal separator. If it has not yet been set explicitly, try to obtain number
* formatting information from locale.
*/
public static function getDecimalSeparator(): string
{
if (!isset(self::$decimalSeparator)) {
$localeconv = localeconv();
self::$decimalSeparator = ($localeconv['decimal_point'] != '')
? $localeconv['decimal_point'] : $localeconv['mon_decimal_point'];
if (self::$decimalSeparator == '') {
// Default to .
self::$decimalSeparator = '.';
}
}
return self::$decimalSeparator;
}
/**
* Set the decimal separator. Only used by NumberFormat::toFormattedString()
* to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
*
* @param ?string $separator Character for decimal separator
*/
public static function setDecimalSeparator(?string $separator): void
{
self::$decimalSeparator = $separator;
}
/**
* Get the thousands separator. If it has not yet been set explicitly, try to obtain number
* formatting information from locale.
*/
public static function getThousandsSeparator(): string
{
if (!isset(self::$thousandsSeparator)) {
$localeconv = localeconv();
self::$thousandsSeparator = ($localeconv['thousands_sep'] != '')
? $localeconv['thousands_sep'] : $localeconv['mon_thousands_sep'];
if (self::$thousandsSeparator == '') {
// Default to .
self::$thousandsSeparator = ',';
}
}
return self::$thousandsSeparator;
}
/**
* Set the thousands separator. Only used by NumberFormat::toFormattedString()
* to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
*
* @param ?string $separator Character for thousands separator
*/
public static function setThousandsSeparator(?string $separator): void
{
self::$thousandsSeparator = $separator;
}
/**
* Get the currency code. If it has not yet been set explicitly, try to obtain the
* symbol information from locale.
*/
public static function getCurrencyCode(): string
{
if (!empty(self::$currencyCode)) {
return self::$currencyCode;
}
self::$currencyCode = '$';
$localeconv = localeconv();
if (!empty($localeconv['currency_symbol'])) {
self::$currencyCode = $localeconv['currency_symbol'];
return self::$currencyCode;
}
if (!empty($localeconv['int_curr_symbol'])) {
self::$currencyCode = $localeconv['int_curr_symbol'];
return self::$currencyCode;
}
return self::$currencyCode;
}
/**
* Set the currency code. Only used by NumberFormat::toFormattedString()
* to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
*
* @param ?string $currencyCode Character for currency code
*/
public static function setCurrencyCode(?string $currencyCode): void
{
self::$currencyCode = $currencyCode;
}
/**
* Convert SYLK encoded string to UTF-8.
*
* @param string $textValue SYLK encoded string
*
* @return string UTF-8 encoded string
*/
public static function SYLKtoUTF8(string $textValue): string
{
self::buildCharacterSets();
// If there is no escape character in the string there is nothing to do
if (!str_contains($textValue, '')) {
return $textValue;
}
foreach (self::$SYLKCharacters as $k => $v) {
$textValue = str_replace($k, $v, $textValue);
}
return $textValue;
}
/**
* Retrieve any leading numeric part of a string, or return the full string if no leading numeric
* (handles basic integer or float, but not exponent or non decimal).
*
* @return float|string string or only the leading numeric part of the string
*/
public static function testStringAsNumeric(string $textValue): float|string
{
if (is_numeric($textValue)) {
return $textValue;
}
$v = (float) $textValue;
return (is_numeric(substr($textValue, 0, strlen((string) $v)))) ? $v : $textValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php 0000644 00000034751 15167673464 0017251 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | Based on OLE::Storage_Lite by Kawai, Takanori |
// +----------------------------------------------------------------------+
//
use PhpOffice\PhpSpreadsheet\Shared\OLE;
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
/**
* Class for creating Root PPS's for OLE containers.
*
* @author Xavier Noguer <xnoguer@php.net>
*/
class Root extends PPS
{
/**
* @var resource
*/
private $fileHandle;
private ?int $smallBlockSize = null;
private ?int $bigBlockSize = null;
/**
* @param null|float|int $time_1st A timestamp
* @param null|float|int $time_2nd A timestamp
* @param File[] $raChild
*/
public function __construct($time_1st, $time_2nd, array $raChild)
{
parent::__construct(null, OLE::ascToUcs('Root Entry'), OLE::OLE_PPS_TYPE_ROOT, null, null, null, $time_1st, $time_2nd, null, $raChild);
}
/**
* Method for saving the whole OLE container (including files).
* In fact, if called with an empty argument (or '-'), it saves to a
* temporary file and then outputs it's contents to stdout.
* If a resource pointer to a stream created by fopen() is passed
* it will be used, but you have to close such stream by yourself.
*
* @param resource $fileHandle the name of the file or stream where to save the OLE container
*
* @return bool true on success
*/
public function save($fileHandle): bool
{
$this->fileHandle = $fileHandle;
// Initial Setting for saving
$this->bigBlockSize = (int) (2 ** (
(isset($this->bigBlockSize)) ? self::adjust2($this->bigBlockSize) : 9
));
$this->smallBlockSize = (int) (2 ** (
(isset($this->smallBlockSize)) ? self::adjust2($this->smallBlockSize) : 6
));
// Make an array of PPS's (for Save)
$aList = [];
PPS::savePpsSetPnt($aList, [$this]);
// calculate values for header
[$iSBDcnt, $iBBcnt, $iPPScnt] = $this->calcSize($aList); //, $rhInfo);
// Save Header
$this->saveHeader((int) $iSBDcnt, (int) $iBBcnt, (int) $iPPScnt);
// Make Small Data string (write SBD)
$this->_data = $this->makeSmallData($aList);
// Write BB
$this->saveBigData((int) $iSBDcnt, $aList);
// Write PPS
$this->savePps($aList);
// Write Big Block Depot and BDList and Adding Header informations
$this->saveBbd((int) $iSBDcnt, (int) $iBBcnt, (int) $iPPScnt);
return true;
}
/**
* Calculate some numbers.
*
* @param array $raList Reference to an array of PPS's
*
* @return float[] The array of numbers
*/
private function calcSize(array &$raList): array
{
// Calculate Basic Setting
[$iSBDcnt, $iBBcnt, $iPPScnt] = [0, 0, 0];
$iSBcnt = 0;
$iCount = count($raList);
for ($i = 0; $i < $iCount; ++$i) {
if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) {
$raList[$i]->Size = $raList[$i]->getDataLen();
if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) {
$iSBcnt += floor($raList[$i]->Size / $this->smallBlockSize)
+ (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0);
} else {
$iBBcnt += (floor($raList[$i]->Size / $this->bigBlockSize)
+ (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0));
}
}
}
$iSmallLen = $iSBcnt * $this->smallBlockSize;
$iSlCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE);
$iSBDcnt = floor($iSBcnt / $iSlCnt) + (($iSBcnt % $iSlCnt) ? 1 : 0);
$iBBcnt += (floor($iSmallLen / $this->bigBlockSize)
+ (($iSmallLen % $this->bigBlockSize) ? 1 : 0));
$iCnt = count($raList);
$iBdCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE;
$iPPScnt = (floor($iCnt / $iBdCnt) + (($iCnt % $iBdCnt) ? 1 : 0));
return [$iSBDcnt, $iBBcnt, $iPPScnt];
}
/**
* Helper function for caculating a magic value for block sizes.
*
* @param int $i2 The argument
*
* @see save()
*/
private static function adjust2(int $i2): float
{
$iWk = log($i2) / log(2);
return ($iWk > floor($iWk)) ? floor($iWk) + 1 : $iWk;
}
/**
* Save OLE header.
*/
private function saveHeader(int $iSBDcnt, int $iBBcnt, int $iPPScnt): void
{
$FILE = $this->fileHandle;
// Calculate Basic Setting
$iBlCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE;
$i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE;
$iBdExL = 0;
$iAll = $iBBcnt + $iPPScnt + $iSBDcnt;
$iAllW = $iAll;
$iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0);
$iBdCnt = floor(($iAll + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0);
// Calculate BD count
if ($iBdCnt > $i1stBdL) {
while (1) {
++$iBdExL;
++$iAllW;
$iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0);
$iBdCnt = floor(($iAllW + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0);
if ($iBdCnt <= ($iBdExL * $iBlCnt + $i1stBdL)) {
break;
}
}
}
// Save Header
fwrite(
$FILE,
"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"
. "\x00\x00\x00\x00"
. "\x00\x00\x00\x00"
. "\x00\x00\x00\x00"
. "\x00\x00\x00\x00"
. pack('v', 0x3B)
. pack('v', 0x03)
. pack('v', -2)
. pack('v', 9)
. pack('v', 6)
. pack('v', 0)
. "\x00\x00\x00\x00"
. "\x00\x00\x00\x00"
. pack('V', $iBdCnt)
. pack('V', $iBBcnt + $iSBDcnt) //ROOT START
. pack('V', 0)
. pack('V', 0x1000)
. pack('V', $iSBDcnt ? 0 : -2) //Small Block Depot
. pack('V', $iSBDcnt)
);
// Extra BDList Start, Count
if ($iBdCnt < $i1stBdL) {
fwrite(
$FILE,
pack('V', -2) // Extra BDList Start
. pack('V', 0)// Extra BDList Count
);
} else {
fwrite($FILE, pack('V', $iAll + $iBdCnt) . pack('V', $iBdExL));
}
// BDList
for ($i = 0; $i < $i1stBdL && $i < $iBdCnt; ++$i) {
fwrite($FILE, pack('V', $iAll + $i));
}
if ($i < $i1stBdL) {
$jB = $i1stBdL - $i;
for ($j = 0; $j < $jB; ++$j) {
fwrite($FILE, (pack('V', -1)));
}
}
}
/**
* Saving big data (PPS's with data bigger than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
*
* @param array $raList Reference to array of PPS's
*/
private function saveBigData(int $iStBlk, array &$raList): void
{
$FILE = $this->fileHandle;
// cycle through PPS's
$iCount = count($raList);
for ($i = 0; $i < $iCount; ++$i) {
if ($raList[$i]->Type != OLE::OLE_PPS_TYPE_DIR) {
$raList[$i]->Size = $raList[$i]->getDataLen();
if (($raList[$i]->Size >= OLE::OLE_DATA_SIZE_SMALL) || (($raList[$i]->Type == OLE::OLE_PPS_TYPE_ROOT) && isset($raList[$i]->_data))) {
fwrite($FILE, $raList[$i]->_data);
if ($raList[$i]->Size % $this->bigBlockSize) {
fwrite($FILE, str_repeat("\x00", $this->bigBlockSize - ($raList[$i]->Size % $this->bigBlockSize)));
}
// Set For PPS
$raList[$i]->startBlock = $iStBlk;
$iStBlk
+= (floor($raList[$i]->Size / $this->bigBlockSize)
+ (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0));
}
}
}
}
/**
* get small data (PPS's with data smaller than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
*
* @param array $raList Reference to array of PPS's
*/
private function makeSmallData(array &$raList): string
{
$sRes = '';
$FILE = $this->fileHandle;
$iSmBlk = 0;
$iCount = count($raList);
for ($i = 0; $i < $iCount; ++$i) {
// Make SBD, small data string
if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) {
if ($raList[$i]->Size <= 0) {
continue;
}
if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) {
$iSmbCnt = floor($raList[$i]->Size / $this->smallBlockSize)
+ (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0);
// Add to SBD
$jB = $iSmbCnt - 1;
for ($j = 0; $j < $jB; ++$j) {
fwrite($FILE, pack('V', $j + $iSmBlk + 1));
}
fwrite($FILE, pack('V', -2));
// Add to Data String(this will be written for RootEntry)
$sRes .= $raList[$i]->_data;
if ($raList[$i]->Size % $this->smallBlockSize) {
$sRes .= str_repeat("\x00", $this->smallBlockSize - ($raList[$i]->Size % $this->smallBlockSize));
}
// Set for PPS
$raList[$i]->startBlock = $iSmBlk;
$iSmBlk += $iSmbCnt;
}
}
}
$iSbCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE);
if ($iSmBlk % $iSbCnt) {
$iB = $iSbCnt - ($iSmBlk % $iSbCnt);
for ($i = 0; $i < $iB; ++$i) {
fwrite($FILE, pack('V', -1));
}
}
return $sRes;
}
/**
* Saves all the PPS's WKs.
*
* @param array $raList Reference to an array with all PPS's
*/
private function savePps(array &$raList): void
{
// Save each PPS WK
$iC = count($raList);
for ($i = 0; $i < $iC; ++$i) {
fwrite($this->fileHandle, $raList[$i]->getPpsWk());
}
// Adjust for Block
$iCnt = count($raList);
$iBCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE;
if ($iCnt % $iBCnt) {
fwrite($this->fileHandle, str_repeat("\x00", ($iBCnt - ($iCnt % $iBCnt)) * OLE::OLE_PPS_SIZE));
}
}
/**
* Saving Big Block Depot.
*/
private function saveBbd(int $iSbdSize, int $iBsize, int $iPpsCnt): void
{
$FILE = $this->fileHandle;
// Calculate Basic Setting
$iBbCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE;
$i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE;
$iBdExL = 0;
$iAll = $iBsize + $iPpsCnt + $iSbdSize;
$iAllW = $iAll;
$iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0);
$iBdCnt = floor(($iAll + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0);
// Calculate BD count
if ($iBdCnt > $i1stBdL) {
while (1) {
++$iBdExL;
++$iAllW;
$iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0);
$iBdCnt = floor(($iAllW + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0);
if ($iBdCnt <= ($iBdExL * $iBbCnt + $i1stBdL)) {
break;
}
}
}
// Making BD
// Set for SBD
if ($iSbdSize > 0) {
for ($i = 0; $i < ($iSbdSize - 1); ++$i) {
fwrite($FILE, pack('V', $i + 1));
}
fwrite($FILE, pack('V', -2));
}
// Set for B
for ($i = 0; $i < ($iBsize - 1); ++$i) {
fwrite($FILE, pack('V', $i + $iSbdSize + 1));
}
fwrite($FILE, pack('V', -2));
// Set for PPS
for ($i = 0; $i < ($iPpsCnt - 1); ++$i) {
fwrite($FILE, pack('V', $i + $iSbdSize + $iBsize + 1));
}
fwrite($FILE, pack('V', -2));
// Set for BBD itself ( 0xFFFFFFFD : BBD)
for ($i = 0; $i < $iBdCnt; ++$i) {
fwrite($FILE, pack('V', 0xFFFFFFFD));
}
// Set for ExtraBDList
for ($i = 0; $i < $iBdExL; ++$i) {
fwrite($FILE, pack('V', 0xFFFFFFFC));
}
// Adjust for Block
if (($iAllW + $iBdCnt) % $iBbCnt) {
$iBlock = ($iBbCnt - (($iAllW + $iBdCnt) % $iBbCnt));
for ($i = 0; $i < $iBlock; ++$i) {
fwrite($FILE, pack('V', -1));
}
}
// Extra BDList
if ($iBdCnt > $i1stBdL) {
$iN = 0;
$iNb = 0;
for ($i = $i1stBdL; $i < $iBdCnt; $i++, ++$iN) {
if ($iN >= ($iBbCnt - 1)) {
$iN = 0;
++$iNb;
fwrite($FILE, pack('V', $iAll + $iBdCnt + $iNb));
}
fwrite($FILE, pack('V', $iBsize + $iSbdSize + $iPpsCnt + $i));
}
if (($iBdCnt - $i1stBdL) % ($iBbCnt - 1)) {
$iB = ($iBbCnt - 1) - (($iBdCnt - $i1stBdL) % ($iBbCnt - 1));
for ($i = 0; $i < $iB; ++$i) {
fwrite($FILE, pack('V', -1));
}
}
fwrite($FILE, pack('V', -2));
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php 0000644 00000004225 15167673464 0017176 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | Based on OLE::Storage_Lite by Kawai, Takanori |
// +----------------------------------------------------------------------+
//
use PhpOffice\PhpSpreadsheet\Shared\OLE;
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
/**
* Class for creating File PPS's for OLE containers.
*
* @author Xavier Noguer <xnoguer@php.net>
*/
class File extends PPS
{
/**
* The constructor.
*
* @param string $name The name of the file (in Unicode)
*
* @see OLE::ascToUcs()
*/
public function __construct(string $name)
{
parent::__construct(null, $name, OLE::OLE_PPS_TYPE_FILE, null, null, null, null, null, '', []);
}
/**
* Initialization method. Has to be called right after OLE_PPS_File().
*/
public function init(): bool
{
return true;
}
/**
* Append data to PPS.
*
* @param string $data The data to append
*/
public function append(string $data): void
{
$this->_data .= $data;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php 0000644 00000013343 15167673464 0021340 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE;
use PhpOffice\PhpSpreadsheet\Shared\OLE;
class ChainedBlockStream
{
/** @var mixed */
public $context;
/**
* The OLE container of the file that is being read.
*/
public ?OLE $ole = null;
/**
* Parameters specified by fopen().
*/
public array $params = [];
/**
* The binary data of the file.
*/
public string $data;
/**
* The file pointer.
*
* @var int byte offset
*/
public int $pos = 0;
/**
* Implements support for fopen().
* For creating streams using this wrapper, use OLE_PPS_File::getStream().
*
* @param string $path resource name including scheme, e.g.
* ole-chainedblockstream://oleInstanceId=1
* @param string $mode only "r" is supported
* @param int $options mask of STREAM_REPORT_ERRORS and STREAM_USE_PATH
* @param ?string $openedPath absolute path of the opened stream (out parameter)
*
* @return bool true on success
*/
public function stream_open(string $path, string $mode, int $options, ?string &$openedPath): bool // @codingStandardsIgnoreLine
{
if ($mode[0] !== 'r') {
if ($options & STREAM_REPORT_ERRORS) {
trigger_error('Only reading is supported', E_USER_WARNING);
}
return false;
}
// 25 is length of "ole-chainedblockstream://"
parse_str(substr($path, 25), $this->params);
if (!isset($this->params['oleInstanceId'], $this->params['blockId'], $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']])) {
if ($options & STREAM_REPORT_ERRORS) {
trigger_error('OLE stream not found', E_USER_WARNING);
}
return false;
}
$this->ole = $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']];
$blockId = $this->params['blockId'];
$this->data = '';
if (isset($this->params['size']) && $this->params['size'] < $this->ole->bigBlockThreshold && $blockId != $this->ole->root->startBlock) {
// Block id refers to small blocks
$rootPos = $this->ole->getBlockOffset($this->ole->root->startBlock);
while ($blockId != -2) {
$pos = $rootPos + $blockId * $this->ole->bigBlockSize;
$blockId = $this->ole->sbat[$blockId];
fseek($this->ole->_file_handle, $pos);
$this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize);
}
} else {
// Block id refers to big blocks
while ($blockId != -2) {
$pos = $this->ole->getBlockOffset($blockId);
fseek($this->ole->_file_handle, $pos);
$this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize);
$blockId = $this->ole->bbat[$blockId];
}
}
if (isset($this->params['size'])) {
$this->data = substr($this->data, 0, $this->params['size']);
}
if ($options & STREAM_USE_PATH) {
$openedPath = $path;
}
return true;
}
/**
* Implements support for fclose().
*/
public function stream_close(): void // @codingStandardsIgnoreLine
{
$this->ole = null;
unset($GLOBALS['_OLE_INSTANCES']);
}
/**
* Implements support for fread(), fgets() etc.
*
* @param int $count maximum number of bytes to read
*
* @return false|string
*/
public function stream_read(int $count): bool|string // @codingStandardsIgnoreLine
{
if ($this->stream_eof()) {
return false;
}
$s = substr($this->data, (int) $this->pos, $count);
$this->pos += $count;
return $s;
}
/**
* Implements support for feof().
*
* @return bool TRUE if the file pointer is at EOF; otherwise FALSE
*/
public function stream_eof(): bool // @codingStandardsIgnoreLine
{
return $this->pos >= strlen($this->data);
}
/**
* Returns the position of the file pointer, i.e. its offset into the file
* stream. Implements support for ftell().
*/
public function stream_tell(): int // @codingStandardsIgnoreLine
{
return $this->pos;
}
/**
* Implements support for fseek().
*
* @param int $offset byte offset
* @param int $whence SEEK_SET, SEEK_CUR or SEEK_END
*/
public function stream_seek(int $offset, int $whence): bool // @codingStandardsIgnoreLine
{
if ($whence == SEEK_SET && $offset >= 0) {
$this->pos = $offset;
} elseif ($whence == SEEK_CUR && -$offset <= $this->pos) {
$this->pos += $offset;
} elseif ($whence == SEEK_END && -$offset <= count($this->data)) { // @phpstan-ignore-line
$this->pos = strlen($this->data) + $offset;
} else {
return false;
}
return true;
}
/**
* Implements support for fstat(). Currently the only supported field is
* "size".
*/
public function stream_stat(): array // @codingStandardsIgnoreLine
{
return [
'size' => strlen($this->data),
];
}
// Methods used by stream_wrapper_register() that are not implemented:
// bool stream_flush ( void )
// int stream_write ( string data )
// bool rename ( string path_from, string path_to )
// bool mkdir ( string path, int mode, int options )
// bool rmdir ( string path, int options )
// bool dir_opendir ( string path, int options )
// array url_stat ( string path, int flags )
// string dir_readdir ( void )
// bool dir_rewinddir ( void )
// bool dir_closedir ( void )
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php 0000644 00000015642 15167673464 0016324 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | Based on OLE::Storage_Lite by Kawai, Takanori |
// +----------------------------------------------------------------------+
//
use PhpOffice\PhpSpreadsheet\Shared\OLE;
/**
* Class for creating PPS's for OLE containers.
*
* @author Xavier Noguer <xnoguer@php.net>
*/
class PPS
{
private const ALL_ONE_BITS = (PHP_INT_SIZE > 4) ? 0xFFFFFFFF : -1;
/**
* The PPS index.
*/
public int $No;
/**
* The PPS name (in Unicode).
*/
public string $Name;
/**
* The PPS type. Dir, Root or File.
*/
public int $Type;
/**
* The index of the previous PPS.
*/
public int $PrevPps;
/**
* The index of the next PPS.
*/
public int $NextPps;
/**
* The index of it's first child if this is a Dir or Root PPS.
*/
public int $DirPps;
/**
* A timestamp.
*/
public float|int $Time1st;
/**
* A timestamp.
*/
public float|int $Time2nd;
/**
* Starting block (small or big) for this PPS's data inside the container.
*/
public ?int $startBlock = null;
/**
* The size of the PPS's data (in bytes).
*/
public int $Size;
/**
* The PPS's data (only used if it's not using a temporary file).
*/
public string $_data = '';
/**
* Array of child PPS's (only used by Root and Dir PPS's).
*/
public array $children = [];
/**
* Pointer to OLE container.
*/
public OLE $ole;
/**
* The constructor.
*
* @param ?int $No The PPS index
* @param ?string $name The PPS name
* @param ?int $type The PPS type. Dir, Root or File
* @param ?int $prev The index of the previous PPS
* @param ?int $next The index of the next PPS
* @param ?int $dir The index of it's first child if this is a Dir or Root PPS
* @param null|float|int $time_1st A timestamp
* @param null|float|int $time_2nd A timestamp
* @param ?string $data The (usually binary) source data of the PPS
* @param array $children Array containing children PPS for this PPS
*/
public function __construct(?int $No, ?string $name, ?int $type, ?int $prev, ?int $next, ?int $dir, $time_1st, $time_2nd, ?string $data, array $children)
{
$this->No = (int) $No;
$this->Name = (string) $name;
$this->Type = (int) $type;
$this->PrevPps = (int) $prev;
$this->NextPps = (int) $next;
$this->DirPps = (int) $dir;
$this->Time1st = $time_1st ?? 0;
$this->Time2nd = $time_2nd ?? 0;
$this->_data = (string) $data;
$this->children = $children;
$this->Size = strlen((string) $data);
}
/**
* Returns the amount of data saved for this PPS.
*
* @return int The amount of data (in bytes)
*/
public function getDataLen(): int
{
//if (!isset($this->_data)) {
// return 0;
//}
return strlen($this->_data);
}
/**
* Returns a string with the PPS's WK (What is a WK?).
*
* @return string The binary string
*/
public function getPpsWk(): string
{
$ret = str_pad($this->Name, 64, "\x00");
$ret .= pack('v', strlen($this->Name) + 2) // 66
. pack('c', $this->Type) // 67
. pack('c', 0x00) //UK // 68
. pack('V', $this->PrevPps) //Prev // 72
. pack('V', $this->NextPps) //Next // 76
. pack('V', $this->DirPps) //Dir // 80
. "\x00\x09\x02\x00" // 84
. "\x00\x00\x00\x00" // 88
. "\xc0\x00\x00\x00" // 92
. "\x00\x00\x00\x46" // 96 // Seems to be ok only for Root
. "\x00\x00\x00\x00" // 100
. OLE::localDateToOLE($this->Time1st) // 108
. OLE::localDateToOLE($this->Time2nd) // 116
. pack('V', $this->startBlock ?? 0) // 120
. pack('V', $this->Size) // 124
. pack('V', 0); // 128
return $ret;
}
/**
* Updates index and pointers to previous, next and children PPS's for this
* PPS. I don't think it'll work with Dir PPS's.
*
* @param array $raList Reference to the array of PPS's for the whole OLE
* container
*
* @return int The index for this PPS
*/
public static function savePpsSetPnt(array &$raList, mixed $to_save, int $depth = 0): int
{
if (!is_array($to_save) || (empty($to_save))) {
return self::ALL_ONE_BITS;
} elseif (count($to_save) == 1) {
$cnt = count($raList);
// If the first entry, it's the root... Don't clone it!
$raList[$cnt] = ($depth == 0) ? $to_save[0] : clone $to_save[0];
$raList[$cnt]->No = $cnt;
$raList[$cnt]->PrevPps = self::ALL_ONE_BITS;
$raList[$cnt]->NextPps = self::ALL_ONE_BITS;
$raList[$cnt]->DirPps = self::savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++);
} else {
$iPos = (int) floor(count($to_save) / 2);
$aPrev = array_slice($to_save, 0, $iPos);
$aNext = array_slice($to_save, $iPos + 1);
$cnt = count($raList);
// If the first entry, it's the root... Don't clone it!
$raList[$cnt] = ($depth == 0) ? $to_save[$iPos] : clone $to_save[$iPos];
$raList[$cnt]->No = $cnt;
$raList[$cnt]->PrevPps = self::savePpsSetPnt($raList, $aPrev, $depth++);
$raList[$cnt]->NextPps = self::savePpsSetPnt($raList, $aNext, $depth++);
$raList[$cnt]->DirPps = self::savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++);
}
return $cnt;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php 0000644 00000007431 15167673464 0020175 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception as SpException;
use PhpOffice\PhpSpreadsheet\Worksheet\Protection;
class PasswordHasher
{
const MAX_PASSWORD_LENGTH = 255;
/**
* Get algorithm name for PHP.
*/
private static function getAlgorithm(string $algorithmName): string
{
if (!$algorithmName) {
return '';
}
// Mapping between algorithm name in Excel and algorithm name in PHP
$mapping = [
Protection::ALGORITHM_MD2 => 'md2',
Protection::ALGORITHM_MD4 => 'md4',
Protection::ALGORITHM_MD5 => 'md5',
Protection::ALGORITHM_SHA_1 => 'sha1',
Protection::ALGORITHM_SHA_256 => 'sha256',
Protection::ALGORITHM_SHA_384 => 'sha384',
Protection::ALGORITHM_SHA_512 => 'sha512',
Protection::ALGORITHM_RIPEMD_128 => 'ripemd128',
Protection::ALGORITHM_RIPEMD_160 => 'ripemd160',
Protection::ALGORITHM_WHIRLPOOL => 'whirlpool',
];
if (array_key_exists($algorithmName, $mapping)) {
return $mapping[$algorithmName];
}
throw new SpException('Unsupported password algorithm: ' . $algorithmName);
}
/**
* Create a password hash from a given string.
*
* This method is based on the spec at:
* https://interoperability.blob.core.windows.net/files/MS-OFFCRYPTO/[MS-OFFCRYPTO].pdf
* 2.3.7.1 Binary Document Password Verifier Derivation Method 1
*
* It replaces a method based on the algorithm provided by
* Daniel Rentz of OpenOffice and the PEAR package
* Spreadsheet_Excel_Writer by Xavier Noguer <xnoguer@rezebra.com>.
*
* @param string $password Password to hash
*/
private static function defaultHashPassword(string $password): string
{
$verifier = 0;
$pwlen = strlen($password);
$passwordArray = pack('c', $pwlen) . $password;
for ($i = $pwlen; $i >= 0; --$i) {
$intermediate1 = (($verifier & 0x4000) === 0) ? 0 : 1;
$intermediate2 = 2 * $verifier;
$intermediate2 = $intermediate2 & 0x7FFF;
$intermediate3 = $intermediate1 | $intermediate2;
$verifier = $intermediate3 ^ ord($passwordArray[$i]);
}
$verifier ^= 0xCE4B;
return strtoupper(dechex($verifier));
}
/**
* Create a password hash from a given string by a specific algorithm.
*
* 2.4.2.4 ISO Write Protection Method
*
* @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f
*
* @param string $password Password to hash
* @param string $algorithm Hash algorithm used to compute the password hash value
* @param string $salt Pseudorandom string
* @param int $spinCount Number of times to iterate on a hash of a password
*
* @return string Hashed password
*/
public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string
{
if (strlen($password) > self::MAX_PASSWORD_LENGTH) {
throw new SpException('Password exceeds ' . self::MAX_PASSWORD_LENGTH . ' characters');
}
$phpAlgorithm = self::getAlgorithm($algorithm);
if (!$phpAlgorithm) {
return self::defaultHashPassword($password);
}
$saltValue = base64_decode($salt);
$encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8');
$hashValue = hash($phpAlgorithm, $saltValue . $encodedPassword, true);
for ($i = 0; $i < $spinCount; ++$i) {
$hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true);
}
return base64_encode($hashValue);
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php 0000644 00000023523 15167673464 0015777 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
/**
* Created by PhpStorm.
* User: Wiktor Trzonkowski
* Date: 6/17/14
* Time: 12:11 PM.
*/
class Axis extends Properties
{
const AXIS_TYPE_CATEGORY = 'catAx';
const AXIS_TYPE_DATE = 'dateAx';
const AXIS_TYPE_VALUE = 'valAx';
const TIME_UNIT_DAYS = 'days';
const TIME_UNIT_MONTHS = 'months';
const TIME_UNIT_YEARS = 'years';
public function __construct()
{
parent::__construct();
$this->fillColor = new ChartColor();
}
/**
* Chart Major Gridlines as.
*/
private ?GridLines $majorGridlines = null;
/**
* Chart Minor Gridlines as.
*/
private ?GridLines $minorGridlines = null;
/**
* Axis Number.
*
* @var mixed[]
*/
private array $axisNumber = [
'format' => self::FORMAT_CODE_GENERAL,
'source_linked' => 1,
'numeric' => null,
];
private string $axisType = '';
private ?AxisText $axisText = null;
private ?Title $dispUnitsTitle = null;
/**
* Axis Options.
*
* @var array<string, null|string>
*/
private array $axisOptions = [
'minimum' => null,
'maximum' => null,
'major_unit' => null,
'minor_unit' => null,
'orientation' => self::ORIENTATION_NORMAL,
'minor_tick_mark' => self::TICK_MARK_NONE,
'major_tick_mark' => self::TICK_MARK_NONE,
'axis_labels' => self::AXIS_LABELS_NEXT_TO,
'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO,
'horizontal_crosses_value' => null,
'textRotation' => null,
'hidden' => null,
'majorTimeUnit' => self::TIME_UNIT_YEARS,
'minorTimeUnit' => self::TIME_UNIT_MONTHS,
'baseTimeUnit' => self::TIME_UNIT_DAYS,
'logBase' => null,
'dispUnitsBuiltIn' => null,
];
public const DISP_UNITS_HUNDREDS = 'hundreds';
public const DISP_UNITS_THOUSANDS = 'thousands';
public const DISP_UNITS_TEN_THOUSANDS = 'tenThousands';
public const DISP_UNITS_HUNDRED_THOUSANDS = 'hundredThousands';
public const DISP_UNITS_MILLIONS = 'millions';
public const DISP_UNITS_TEN_MILLIONS = 'tenMillions';
public const DISP_UNITS_HUNDRED_MILLIONS = 'hundredMillions';
public const DISP_UNITS_BILLIONS = 'billions';
public const DISP_UNITS_TRILLIONS = 'trillions';
public const TRILLION_INDEX = (PHP_INT_SIZE > 4) ? 1000000000000 : '1000000000000';
public const DISP_UNITS_BUILTIN_INT = [
100 => self::DISP_UNITS_HUNDREDS,
1000 => self::DISP_UNITS_THOUSANDS,
10000 => self::DISP_UNITS_TEN_THOUSANDS,
100000 => self::DISP_UNITS_HUNDRED_THOUSANDS,
1000000 => self::DISP_UNITS_MILLIONS,
10000000 => self::DISP_UNITS_TEN_MILLIONS,
100000000 => self::DISP_UNITS_HUNDRED_MILLIONS,
1000000000 => self::DISP_UNITS_BILLIONS,
self::TRILLION_INDEX => self::DISP_UNITS_TRILLIONS, // overflow for 32-bit
];
/**
* Fill Properties.
*/
private ChartColor $fillColor;
private const NUMERIC_FORMAT = [
Properties::FORMAT_CODE_NUMBER,
Properties::FORMAT_CODE_DATE,
Properties::FORMAT_CODE_DATE_ISO8601,
];
private bool $noFill = false;
/**
* Get Series Data Type.
*/
public function setAxisNumberProperties(string $format_code, ?bool $numeric = null, int $sourceLinked = 0): void
{
$format = $format_code;
$this->axisNumber['format'] = $format;
$this->axisNumber['source_linked'] = $sourceLinked;
if (is_bool($numeric)) {
$this->axisNumber['numeric'] = $numeric;
} elseif (in_array($format, self::NUMERIC_FORMAT, true)) {
$this->axisNumber['numeric'] = true;
}
}
/**
* Get Axis Number Format Data Type.
*/
public function getAxisNumberFormat(): string
{
return $this->axisNumber['format'];
}
/**
* Get Axis Number Source Linked.
*/
public function getAxisNumberSourceLinked(): string
{
return (string) $this->axisNumber['source_linked'];
}
public function getAxisIsNumericFormat(): bool
{
return $this->axisType === self::AXIS_TYPE_DATE || (bool) $this->axisNumber['numeric'];
}
public function setAxisOption(string $key, null|float|int|string $value): void
{
if ($value !== null && $value !== '') {
$this->axisOptions[$key] = (string) $value;
}
}
/**
* Set Axis Options Properties.
*/
public function setAxisOptionsProperties(
string $axisLabels,
?string $horizontalCrossesValue = null,
?string $horizontalCrosses = null,
?string $axisOrientation = null,
?string $majorTmt = null,
?string $minorTmt = null,
null|float|int|string $minimum = null,
null|float|int|string $maximum = null,
null|float|int|string $majorUnit = null,
null|float|int|string $minorUnit = null,
null|float|int|string $textRotation = null,
?string $hidden = null,
?string $baseTimeUnit = null,
?string $majorTimeUnit = null,
?string $minorTimeUnit = null,
null|float|int|string $logBase = null,
?string $dispUnitsBuiltIn = null
): void {
$this->axisOptions['axis_labels'] = $axisLabels;
$this->setAxisOption('horizontal_crosses_value', $horizontalCrossesValue);
$this->setAxisOption('horizontal_crosses', $horizontalCrosses);
$this->setAxisOption('orientation', $axisOrientation);
$this->setAxisOption('major_tick_mark', $majorTmt);
$this->setAxisOption('minor_tick_mark', $minorTmt);
$this->setAxisOption('minimum', $minimum);
$this->setAxisOption('maximum', $maximum);
$this->setAxisOption('major_unit', $majorUnit);
$this->setAxisOption('minor_unit', $minorUnit);
$this->setAxisOption('textRotation', $textRotation);
$this->setAxisOption('hidden', $hidden);
$this->setAxisOption('baseTimeUnit', $baseTimeUnit);
$this->setAxisOption('majorTimeUnit', $majorTimeUnit);
$this->setAxisOption('minorTimeUnit', $minorTimeUnit);
$this->setAxisOption('logBase', $logBase);
$this->setAxisOption('dispUnitsBuiltIn', $dispUnitsBuiltIn);
}
/**
* Get Axis Options Property.
*/
public function getAxisOptionsProperty(string $property): ?string
{
if ($property === 'textRotation') {
if ($this->axisText !== null) {
if ($this->axisText->getRotation() !== null) {
return (string) $this->axisText->getRotation();
}
}
}
return $this->axisOptions[$property];
}
/**
* Set Axis Orientation Property.
*/
public function setAxisOrientation(string $orientation): void
{
$this->axisOptions['orientation'] = (string) $orientation;
}
public function getAxisType(): string
{
return $this->axisType;
}
public function setAxisType(string $type): self
{
if ($type === self::AXIS_TYPE_CATEGORY || $type === self::AXIS_TYPE_VALUE || $type === self::AXIS_TYPE_DATE) {
$this->axisType = $type;
} else {
$this->axisType = '';
}
return $this;
}
/**
* Set Fill Property.
*/
public function setFillParameters(?string $color, ?int $alpha = null, ?string $AlphaType = ChartColor::EXCEL_COLOR_TYPE_RGB): void
{
$this->fillColor->setColorProperties($color, $alpha, $AlphaType);
}
/**
* Get Fill Property.
*/
public function getFillProperty(string $property): string
{
return (string) $this->fillColor->getColorProperty($property);
}
public function getFillColorObject(): ChartColor
{
return $this->fillColor;
}
private string $crossBetween = ''; // 'between' or 'midCat' might be better
public function setCrossBetween(string $crossBetween): self
{
$this->crossBetween = $crossBetween;
return $this;
}
public function getCrossBetween(): string
{
return $this->crossBetween;
}
public function getMajorGridlines(): ?GridLines
{
return $this->majorGridlines;
}
public function getMinorGridlines(): ?GridLines
{
return $this->minorGridlines;
}
public function setMajorGridlines(?GridLines $gridlines): self
{
$this->majorGridlines = $gridlines;
return $this;
}
public function setMinorGridlines(?GridLines $gridlines): self
{
$this->minorGridlines = $gridlines;
return $this;
}
public function getAxisText(): ?AxisText
{
return $this->axisText;
}
public function setAxisText(?AxisText $axisText): self
{
$this->axisText = $axisText;
return $this;
}
public function setNoFill(bool $noFill): self
{
$this->noFill = $noFill;
return $this;
}
public function getNoFill(): bool
{
return $this->noFill;
}
public function setDispUnitsTitle(?Title $dispUnitsTitle): self
{
$this->dispUnitsTitle = $dispUnitsTitle;
return $this;
}
public function getDispUnitsTitle(): ?Title
{
return $this->dispUnitsTitle;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
parent::__clone();
$this->majorGridlines = ($this->majorGridlines === null) ? null : clone $this->majorGridlines;
$this->majorGridlines = ($this->minorGridlines === null) ? null : clone $this->minorGridlines;
$this->axisText = ($this->axisText === null) ? null : clone $this->axisText;
$this->dispUnitsTitle = ($this->dispUnitsTitle === null) ? null : clone $this->dispUnitsTitle;
$this->fillColor = clone $this->fillColor;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php 0000644 00000033723 15167673464 0020302 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class DataSeriesValues extends Properties
{
const DATASERIES_TYPE_STRING = 'String';
const DATASERIES_TYPE_NUMBER = 'Number';
private const DATA_TYPE_VALUES = [
self::DATASERIES_TYPE_STRING,
self::DATASERIES_TYPE_NUMBER,
];
/**
* Series Data Type.
*/
private string $dataType;
/**
* Series Data Source.
*/
private ?string $dataSource;
/**
* Format Code.
*/
private ?string $formatCode;
/**
* Series Point Marker.
*/
private ?string $pointMarker;
private ChartColor $markerFillColor;
private ChartColor $markerBorderColor;
/**
* Series Point Size.
*/
private int $pointSize = 3;
/**
* Point Count (The number of datapoints in the dataseries).
*/
private int $pointCount;
/**
* Data Values.
*/
private ?array $dataValues;
/**
* Fill color (can be array with colors if dataseries have custom colors).
*
* @var null|ChartColor|ChartColor[]
*/
private $fillColor;
private bool $scatterLines = true;
private bool $bubble3D = false;
private ?Layout $labelLayout = null;
/** @var TrendLine[] */
private array $trendLines = [];
/**
* Create a new DataSeriesValues object.
*
* @param null|ChartColor|ChartColor[]|string|string[] $fillColor
*/
public function __construct(
string $dataType = self::DATASERIES_TYPE_NUMBER,
?string $dataSource = null,
?string $formatCode = null,
int $pointCount = 0,
?array $dataValues = [],
?string $marker = null,
null|ChartColor|array|string $fillColor = null,
int|string $pointSize = 3
) {
parent::__construct();
$this->markerFillColor = new ChartColor();
$this->markerBorderColor = new ChartColor();
$this->setDataType($dataType);
$this->dataSource = $dataSource;
$this->formatCode = $formatCode;
$this->pointCount = $pointCount;
$this->dataValues = $dataValues;
$this->pointMarker = $marker;
if ($fillColor !== null) {
$this->setFillColor($fillColor);
}
if (is_numeric($pointSize)) {
$this->pointSize = (int) $pointSize;
}
}
/**
* Get Series Data Type.
*/
public function getDataType(): string
{
return $this->dataType;
}
/**
* Set Series Data Type.
*
* @param string $dataType Datatype of this data series
* Typical values are:
* DataSeriesValues::DATASERIES_TYPE_STRING
* Normally used for axis point values
* DataSeriesValues::DATASERIES_TYPE_NUMBER
* Normally used for chart data values
*
* @return $this
*/
public function setDataType(string $dataType): static
{
if (!in_array($dataType, self::DATA_TYPE_VALUES)) {
throw new Exception('Invalid datatype for chart data series values');
}
$this->dataType = $dataType;
return $this;
}
/**
* Get Series Data Source (formula).
*/
public function getDataSource(): ?string
{
return $this->dataSource;
}
/**
* Set Series Data Source (formula).
*
* @return $this
*/
public function setDataSource(?string $dataSource): static
{
$this->dataSource = $dataSource;
return $this;
}
/**
* Get Point Marker.
*/
public function getPointMarker(): ?string
{
return $this->pointMarker;
}
/**
* Set Point Marker.
*
* @return $this
*/
public function setPointMarker(string $marker): static
{
$this->pointMarker = $marker;
return $this;
}
public function getMarkerFillColor(): ChartColor
{
return $this->markerFillColor;
}
public function getMarkerBorderColor(): ChartColor
{
return $this->markerBorderColor;
}
/**
* Get Point Size.
*/
public function getPointSize(): int
{
return $this->pointSize;
}
/**
* Set Point Size.
*
* @return $this
*/
public function setPointSize(int $size = 3): static
{
$this->pointSize = $size;
return $this;
}
/**
* Get Series Format Code.
*/
public function getFormatCode(): ?string
{
return $this->formatCode;
}
/**
* Set Series Format Code.
*
* @return $this
*/
public function setFormatCode(string $formatCode): static
{
$this->formatCode = $formatCode;
return $this;
}
/**
* Get Series Point Count.
*/
public function getPointCount(): int
{
return $this->pointCount;
}
/**
* Get fill color object.
*
* @return null|ChartColor|ChartColor[]
*/
public function getFillColorObject()
{
return $this->fillColor;
}
private function stringToChartColor(string $fillString): ChartColor
{
$value = $type = '';
if (str_starts_with($fillString, '*')) {
$type = 'schemeClr';
$value = substr($fillString, 1);
} elseif (str_starts_with($fillString, '/')) {
$type = 'prstClr';
$value = substr($fillString, 1);
} elseif ($fillString !== '') {
$type = 'srgbClr';
$value = $fillString;
$this->validateColor($value);
}
return new ChartColor($value, null, $type);
}
private function chartColorToString(ChartColor $chartColor): string
{
$type = (string) $chartColor->getColorProperty('type');
$value = (string) $chartColor->getColorProperty('value');
if ($type === '' || $value === '') {
return '';
}
if ($type === 'schemeClr') {
return "*$value";
}
if ($type === 'prstClr') {
return "/$value";
}
return $value;
}
/**
* Get fill color.
*
* @return string|string[] HEX color or array with HEX colors
*/
public function getFillColor(): string|array
{
if ($this->fillColor === null) {
return '';
}
if (is_array($this->fillColor)) {
$array = [];
foreach ($this->fillColor as $chartColor) {
$array[] = $this->chartColorToString($chartColor);
}
return $array;
}
return $this->chartColorToString($this->fillColor);
}
/**
* Set fill color for series.
*
* @param ChartColor|ChartColor[]|string|string[] $color HEX color or array with HEX colors
*
* @return $this
*/
public function setFillColor($color): static
{
if (is_array($color)) {
$this->fillColor = [];
foreach ($color as $fillString) {
if ($fillString instanceof ChartColor) {
$this->fillColor[] = $fillString;
} else {
$this->fillColor[] = $this->stringToChartColor($fillString);
}
}
} elseif ($color instanceof ChartColor) {
$this->fillColor = $color;
} else {
$this->fillColor = $this->stringToChartColor($color);
}
return $this;
}
/**
* Method for validating hex color.
*
* @param string $color value for color
*
* @return bool true if validation was successful
*/
private function validateColor(string $color): bool
{
if (!preg_match('/^[a-f0-9]{6}$/i', $color)) {
throw new Exception(sprintf('Invalid hex color for chart series (color: "%s")', $color));
}
return true;
}
/**
* Get line width for series.
*/
public function getLineWidth(): null|float|int
{
return $this->lineStyleProperties['width'];
}
/**
* Set line width for the series.
*
* @return $this
*/
public function setLineWidth(null|float|int $width): static
{
$this->lineStyleProperties['width'] = $width;
return $this;
}
/**
* Identify if the Data Series is a multi-level or a simple series.
*/
public function isMultiLevelSeries(): ?bool
{
if (!empty($this->dataValues)) {
return is_array(array_values($this->dataValues)[0]);
}
return null;
}
/**
* Return the level count of a multi-level Data Series.
*/
public function multiLevelCount(): int
{
$levelCount = 0;
foreach (($this->dataValues ?? []) as $dataValueSet) {
$levelCount = max($levelCount, count($dataValueSet));
}
return $levelCount;
}
/**
* Get Series Data Values.
*/
public function getDataValues(): ?array
{
return $this->dataValues;
}
/**
* Get the first Series Data value.
*/
public function getDataValue(): mixed
{
if ($this->dataValues === null) {
return null;
}
$count = count($this->dataValues);
if ($count == 0) {
return null;
} elseif ($count == 1) {
return $this->dataValues[0];
}
return $this->dataValues;
}
/**
* Set Series Data Values.
*
* @return $this
*/
public function setDataValues(array $dataValues): static
{
$this->dataValues = Functions::flattenArray($dataValues);
$this->pointCount = count($dataValues);
return $this;
}
public function refresh(Worksheet $worksheet, bool $flatten = true): void
{
if ($this->dataSource !== null) {
$calcEngine = Calculation::getInstance($worksheet->getParent());
$newDataValues = Calculation::unwrapResult(
$calcEngine->_calculateFormulaValue(
'=' . $this->dataSource,
null,
$worksheet->getCell('A1')
)
);
if ($flatten) {
$this->dataValues = Functions::flattenArray($newDataValues);
foreach ($this->dataValues as &$dataValue) {
if (is_string($dataValue) && !empty($dataValue) && $dataValue[0] == '#') {
$dataValue = 0.0;
}
}
unset($dataValue);
} else {
[$worksheet, $cellRange] = Worksheet::extractSheetTitle($this->dataSource, true);
$dimensions = Coordinate::rangeDimension(str_replace('$', '', $cellRange ?? ''));
if (($dimensions[0] == 1) || ($dimensions[1] == 1)) {
$this->dataValues = Functions::flattenArray($newDataValues);
} else {
$newArray = array_values(array_shift($newDataValues));
foreach ($newArray as $i => $newDataSet) {
$newArray[$i] = [$newDataSet];
}
foreach ($newDataValues as $newDataSet) {
$i = 0;
foreach ($newDataSet as $newDataVal) {
array_unshift($newArray[$i++], $newDataVal);
}
}
$this->dataValues = $newArray;
}
}
$this->pointCount = count($this->dataValues);
}
}
public function getScatterLines(): bool
{
return $this->scatterLines;
}
public function setScatterLines(bool $scatterLines): self
{
$this->scatterLines = $scatterLines;
return $this;
}
public function getBubble3D(): bool
{
return $this->bubble3D;
}
public function setBubble3D(bool $bubble3D): self
{
$this->bubble3D = $bubble3D;
return $this;
}
/**
* Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
*/
private bool $smoothLine = false;
/**
* Get Smooth Line.
*/
public function getSmoothLine(): bool
{
return $this->smoothLine;
}
/**
* Set Smooth Line.
*
* @return $this
*/
public function setSmoothLine(bool $smoothLine): static
{
$this->smoothLine = $smoothLine;
return $this;
}
public function getLabelLayout(): ?Layout
{
return $this->labelLayout;
}
public function setLabelLayout(?Layout $labelLayout): self
{
$this->labelLayout = $labelLayout;
return $this;
}
public function setTrendLines(array $trendLines): self
{
$this->trendLines = $trendLines;
return $this;
}
public function getTrendLines(): array
{
return $this->trendLines;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
parent::__clone();
$this->markerFillColor = clone $this->markerFillColor;
$this->markerBorderColor = clone $this->markerBorderColor;
if (is_array($this->fillColor)) {
$fillColor = $this->fillColor;
$this->fillColor = [];
foreach ($fillColor as $color) {
$this->fillColor[] = clone $color;
}
} elseif ($this->fillColor instanceof ChartColor) {
$this->fillColor = clone $this->fillColor;
}
$this->labelLayout = ($this->labelLayout === null) ? null : clone $this->labelLayout;
$trendLines = $this->trendLines;
$this->trendLines = [];
foreach ($trendLines as $trendLine) {
$this->trendLines[] = clone $trendLine;
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php 0000644 00000071346 15167673464 0017235 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
/**
* Created by PhpStorm.
* User: nhw2h8s
* Date: 7/2/14
* Time: 5:45 PM.
*/
abstract class Properties
{
const AXIS_LABELS_LOW = 'low';
const AXIS_LABELS_HIGH = 'high';
const AXIS_LABELS_NEXT_TO = 'nextTo';
const AXIS_LABELS_NONE = 'none';
const TICK_MARK_NONE = 'none';
const TICK_MARK_INSIDE = 'in';
const TICK_MARK_OUTSIDE = 'out';
const TICK_MARK_CROSS = 'cross';
const HORIZONTAL_CROSSES_AUTOZERO = 'autoZero';
const HORIZONTAL_CROSSES_MAXIMUM = 'max';
const FORMAT_CODE_GENERAL = 'General';
const FORMAT_CODE_NUMBER = '#,##0.00';
const FORMAT_CODE_CURRENCY = '$#,##0.00';
const FORMAT_CODE_ACCOUNTING = '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)';
const FORMAT_CODE_DATE = 'm/d/yyyy';
const FORMAT_CODE_DATE_ISO8601 = 'yyyy-mm-dd';
const FORMAT_CODE_TIME = '[$-F400]h:mm:ss AM/PM';
const FORMAT_CODE_PERCENTAGE = '0.00%';
const FORMAT_CODE_FRACTION = '# ?/?';
const FORMAT_CODE_SCIENTIFIC = '0.00E+00';
const FORMAT_CODE_TEXT = '@';
const FORMAT_CODE_SPECIAL = '00000';
const ORIENTATION_NORMAL = 'minMax';
const ORIENTATION_REVERSED = 'maxMin';
const LINE_STYLE_COMPOUND_SIMPLE = 'sng';
const LINE_STYLE_COMPOUND_DOUBLE = 'dbl';
const LINE_STYLE_COMPOUND_THICKTHIN = 'thickThin';
const LINE_STYLE_COMPOUND_THINTHICK = 'thinThick';
const LINE_STYLE_COMPOUND_TRIPLE = 'tri';
const LINE_STYLE_DASH_SOLID = 'solid';
const LINE_STYLE_DASH_ROUND_DOT = 'sysDot';
const LINE_STYLE_DASH_SQUARE_DOT = 'sysDash';
const LINE_STYPE_DASH_DASH = 'dash';
const LINE_STYLE_DASH_DASH_DOT = 'dashDot';
const LINE_STYLE_DASH_LONG_DASH = 'lgDash';
const LINE_STYLE_DASH_LONG_DASH_DOT = 'lgDashDot';
const LINE_STYLE_DASH_LONG_DASH_DOT_DOT = 'lgDashDotDot';
const LINE_STYLE_CAP_SQUARE = 'sq';
const LINE_STYLE_CAP_ROUND = 'rnd';
const LINE_STYLE_CAP_FLAT = 'flat';
const LINE_STYLE_JOIN_ROUND = 'round';
const LINE_STYLE_JOIN_MITER = 'miter';
const LINE_STYLE_JOIN_BEVEL = 'bevel';
const LINE_STYLE_ARROW_TYPE_NOARROW = null;
const LINE_STYLE_ARROW_TYPE_ARROW = 'triangle';
const LINE_STYLE_ARROW_TYPE_OPEN = 'arrow';
const LINE_STYLE_ARROW_TYPE_STEALTH = 'stealth';
const LINE_STYLE_ARROW_TYPE_DIAMOND = 'diamond';
const LINE_STYLE_ARROW_TYPE_OVAL = 'oval';
const LINE_STYLE_ARROW_SIZE_1 = 1;
const LINE_STYLE_ARROW_SIZE_2 = 2;
const LINE_STYLE_ARROW_SIZE_3 = 3;
const LINE_STYLE_ARROW_SIZE_4 = 4;
const LINE_STYLE_ARROW_SIZE_5 = 5;
const LINE_STYLE_ARROW_SIZE_6 = 6;
const LINE_STYLE_ARROW_SIZE_7 = 7;
const LINE_STYLE_ARROW_SIZE_8 = 8;
const LINE_STYLE_ARROW_SIZE_9 = 9;
const SHADOW_PRESETS_NOSHADOW = null;
const SHADOW_PRESETS_OUTER_BOTTTOM_RIGHT = 1;
const SHADOW_PRESETS_OUTER_BOTTOM = 2;
const SHADOW_PRESETS_OUTER_BOTTOM_LEFT = 3;
const SHADOW_PRESETS_OUTER_RIGHT = 4;
const SHADOW_PRESETS_OUTER_CENTER = 5;
const SHADOW_PRESETS_OUTER_LEFT = 6;
const SHADOW_PRESETS_OUTER_TOP_RIGHT = 7;
const SHADOW_PRESETS_OUTER_TOP = 8;
const SHADOW_PRESETS_OUTER_TOP_LEFT = 9;
const SHADOW_PRESETS_INNER_BOTTTOM_RIGHT = 10;
const SHADOW_PRESETS_INNER_BOTTOM = 11;
const SHADOW_PRESETS_INNER_BOTTOM_LEFT = 12;
const SHADOW_PRESETS_INNER_RIGHT = 13;
const SHADOW_PRESETS_INNER_CENTER = 14;
const SHADOW_PRESETS_INNER_LEFT = 15;
const SHADOW_PRESETS_INNER_TOP_RIGHT = 16;
const SHADOW_PRESETS_INNER_TOP = 17;
const SHADOW_PRESETS_INNER_TOP_LEFT = 18;
const SHADOW_PRESETS_PERSPECTIVE_BELOW = 19;
const SHADOW_PRESETS_PERSPECTIVE_UPPER_RIGHT = 20;
const SHADOW_PRESETS_PERSPECTIVE_UPPER_LEFT = 21;
const SHADOW_PRESETS_PERSPECTIVE_LOWER_RIGHT = 22;
const SHADOW_PRESETS_PERSPECTIVE_LOWER_LEFT = 23;
const POINTS_WIDTH_MULTIPLIER = 12700;
const ANGLE_MULTIPLIER = 60000; // direction and size-kx size-ky
const PERCENTAGE_MULTIPLIER = 100000; // size sx and sy
protected bool $objectState = false; // used only for minor gridlines
/** @var ?float */
protected ?float $glowSize = null;
protected ChartColor $glowColor;
protected array $softEdges = [
'size' => null,
];
protected array $shadowProperties = self::PRESETS_OPTIONS[0];
protected ChartColor $shadowColor;
public function __construct()
{
$this->lineColor = new ChartColor();
$this->glowColor = new ChartColor();
$this->shadowColor = new ChartColor();
$this->shadowColor->setType(ChartColor::EXCEL_COLOR_TYPE_STANDARD);
$this->shadowColor->setValue('black');
$this->shadowColor->setAlpha(40);
}
/**
* Get Object State.
*/
public function getObjectState(): bool
{
return $this->objectState;
}
/**
* Change Object State to True.
*
* @return $this
*/
public function activateObject()
{
$this->objectState = true;
return $this;
}
public static function pointsToXml(float $width): string
{
return (string) (int) ($width * self::POINTS_WIDTH_MULTIPLIER);
}
public static function xmlToPoints(string $width): float
{
return ((float) $width) / self::POINTS_WIDTH_MULTIPLIER;
}
public static function angleToXml(float $angle): string
{
return (string) (int) ($angle * self::ANGLE_MULTIPLIER);
}
public static function xmlToAngle(string $angle): float
{
return ((float) $angle) / self::ANGLE_MULTIPLIER;
}
public static function tenthOfPercentToXml(float $value): string
{
return (string) (int) ($value * self::PERCENTAGE_MULTIPLIER);
}
public static function xmlToTenthOfPercent(string $value): float
{
return ((float) $value) / self::PERCENTAGE_MULTIPLIER;
}
protected function setColorProperties(?string $color, null|float|int|string $alpha, ?string $colorType): array
{
return [
'type' => $colorType,
'value' => $color,
'alpha' => ($alpha === null) ? null : (int) $alpha,
];
}
protected const PRESETS_OPTIONS = [
//NONE
0 => [
'presets' => self::SHADOW_PRESETS_NOSHADOW,
'effect' => null,
//'color' => [
// 'type' => ChartColor::EXCEL_COLOR_TYPE_STANDARD,
// 'value' => 'black',
// 'alpha' => 40,
//],
'size' => [
'sx' => null,
'sy' => null,
'kx' => null,
'ky' => null,
],
'blur' => null,
'direction' => null,
'distance' => null,
'algn' => null,
'rotWithShape' => null,
],
//OUTER
1 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 2700000 / self::ANGLE_MULTIPLIER,
'algn' => 'tl',
'rotWithShape' => '0',
],
2 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 5400000 / self::ANGLE_MULTIPLIER,
'algn' => 't',
'rotWithShape' => '0',
],
3 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 8100000 / self::ANGLE_MULTIPLIER,
'algn' => 'tr',
'rotWithShape' => '0',
],
4 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'algn' => 'l',
'rotWithShape' => '0',
],
5 => [
'effect' => 'outerShdw',
'size' => [
'sx' => 102000 / self::PERCENTAGE_MULTIPLIER,
'sy' => 102000 / self::PERCENTAGE_MULTIPLIER,
],
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'algn' => 'ctr',
'rotWithShape' => '0',
],
6 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 10800000 / self::ANGLE_MULTIPLIER,
'algn' => 'r',
'rotWithShape' => '0',
],
7 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 18900000 / self::ANGLE_MULTIPLIER,
'algn' => 'bl',
'rotWithShape' => '0',
],
8 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 16200000 / self::ANGLE_MULTIPLIER,
'rotWithShape' => '0',
],
9 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 13500000 / self::ANGLE_MULTIPLIER,
'algn' => 'br',
'rotWithShape' => '0',
],
//INNER
10 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 2700000 / self::ANGLE_MULTIPLIER,
],
11 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 5400000 / self::ANGLE_MULTIPLIER,
],
12 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 8100000 / self::ANGLE_MULTIPLIER,
],
13 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
],
14 => [
'effect' => 'innerShdw',
'blur' => 114300 / self::POINTS_WIDTH_MULTIPLIER,
],
15 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 10800000 / self::ANGLE_MULTIPLIER,
],
16 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 18900000 / self::ANGLE_MULTIPLIER,
],
17 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 16200000 / self::ANGLE_MULTIPLIER,
],
18 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 13500000 / self::ANGLE_MULTIPLIER,
],
//perspective
19 => [
'effect' => 'outerShdw',
'blur' => 152400 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 317500 / self::POINTS_WIDTH_MULTIPLIER,
'size' => [
'sx' => 90000 / self::PERCENTAGE_MULTIPLIER,
'sy' => -19000 / self::PERCENTAGE_MULTIPLIER,
],
'direction' => 5400000 / self::ANGLE_MULTIPLIER,
'rotWithShape' => '0',
],
20 => [
'effect' => 'outerShdw',
'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 18900000 / self::ANGLE_MULTIPLIER,
'size' => [
'sy' => 23000 / self::PERCENTAGE_MULTIPLIER,
'kx' => -1200000 / self::ANGLE_MULTIPLIER,
],
'algn' => 'bl',
'rotWithShape' => '0',
],
21 => [
'effect' => 'outerShdw',
'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 13500000 / self::ANGLE_MULTIPLIER,
'size' => [
'sy' => 23000 / self::PERCENTAGE_MULTIPLIER,
'kx' => 1200000 / self::ANGLE_MULTIPLIER,
],
'algn' => 'br',
'rotWithShape' => '0',
],
22 => [
'effect' => 'outerShdw',
'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 12700 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 2700000 / self::ANGLE_MULTIPLIER,
'size' => [
'sy' => -23000 / self::PERCENTAGE_MULTIPLIER,
'kx' => -800400 / self::ANGLE_MULTIPLIER,
],
'algn' => 'bl',
'rotWithShape' => '0',
],
23 => [
'effect' => 'outerShdw',
'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 12700 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 8100000 / self::ANGLE_MULTIPLIER,
'size' => [
'sy' => -23000 / self::PERCENTAGE_MULTIPLIER,
'kx' => 800400 / self::ANGLE_MULTIPLIER,
],
'algn' => 'br',
'rotWithShape' => '0',
],
];
protected function getShadowPresetsMap(int $presetsOption): array
{
return self::PRESETS_OPTIONS[$presetsOption] ?? self::PRESETS_OPTIONS[0];
}
/**
* Get value of array element.
*/
protected function getArrayElementsValue(array $properties, array|int|string $elements): mixed
{
$reference = &$properties;
if (!is_array($elements)) {
return $reference[$elements];
}
foreach ($elements as $keys) {
$reference = &$reference[$keys];
}
return $reference;
}
/**
* Set Glow Properties.
*/
public function setGlowProperties(float $size, ?string $colorValue = null, ?int $colorAlpha = null, ?string $colorType = null): void
{
$this
->activateObject()
->setGlowSize($size);
$this->glowColor->setColorPropertiesArray(
[
'value' => $colorValue,
'type' => $colorType,
'alpha' => $colorAlpha,
]
);
}
/**
* Get Glow Property.
*/
public function getGlowProperty(array|string $property): null|array|float|int|string
{
$retVal = null;
if ($property === 'size') {
$retVal = $this->glowSize;
} elseif ($property === 'color') {
$retVal = [
'value' => $this->glowColor->getColorProperty('value'),
'type' => $this->glowColor->getColorProperty('type'),
'alpha' => $this->glowColor->getColorProperty('alpha'),
];
} elseif (is_array($property) && count($property) >= 2 && $property[0] === 'color') {
$retVal = $this->glowColor->getColorProperty($property[1]);
}
return $retVal;
}
/**
* Get Glow Color Property.
*/
public function getGlowColor(string $propertyName): null|int|string
{
return $this->glowColor->getColorProperty($propertyName);
}
public function getGlowColorObject(): ChartColor
{
return $this->glowColor;
}
/**
* Get Glow Size.
*/
public function getGlowSize(): ?float
{
return $this->glowSize;
}
/**
* Set Glow Size.
*
* @return $this
*/
protected function setGlowSize(?float $size)
{
$this->glowSize = $size;
return $this;
}
/**
* Set Soft Edges Size.
*/
public function setSoftEdges(?float $size): void
{
if ($size !== null) {
$this->activateObject();
$this->softEdges['size'] = $size;
}
}
/**
* Get Soft Edges Size.
*/
public function getSoftEdgesSize(): ?float
{
return $this->softEdges['size'];
}
public function setShadowProperty(string $propertyName, mixed $value): self
{
$this->activateObject();
if ($propertyName === 'color' && is_array($value)) {
$this->shadowColor->setColorPropertiesArray($value);
} else {
$this->shadowProperties[$propertyName] = $value;
}
return $this;
}
/**
* Set Shadow Properties.
*/
public function setShadowProperties(int $presets, ?string $colorValue = null, ?string $colorType = null, null|float|int|string $colorAlpha = null, ?float $blur = null, ?int $angle = null, ?float $distance = null): void
{
$this->activateObject()->setShadowPresetsProperties((int) $presets);
if ($presets === 0) {
$this->shadowColor->setType(ChartColor::EXCEL_COLOR_TYPE_STANDARD);
$this->shadowColor->setValue('black');
$this->shadowColor->setAlpha(40);
}
if ($colorValue !== null) {
$this->shadowColor->setValue($colorValue);
}
if ($colorType !== null) {
$this->shadowColor->setType($colorType);
}
if (is_numeric($colorAlpha)) {
$this->shadowColor->setAlpha((int) $colorAlpha);
}
$this
->setShadowBlur($blur)
->setShadowAngle($angle)
->setShadowDistance($distance);
}
/**
* Set Shadow Presets Properties.
*
* @return $this
*/
protected function setShadowPresetsProperties(int $presets)
{
$this->shadowProperties['presets'] = $presets;
$this->setShadowPropertiesMapValues($this->getShadowPresetsMap($presets));
return $this;
}
protected const SHADOW_ARRAY_KEYS = ['size', 'color'];
/**
* Set Shadow Properties Values.
*
* @return $this
*/
protected function setShadowPropertiesMapValues(array $propertiesMap, ?array &$reference = null)
{
$base_reference = $reference;
foreach ($propertiesMap as $property_key => $property_val) {
if (is_array($property_val)) {
if (in_array($property_key, self::SHADOW_ARRAY_KEYS, true)) {
$reference = &$this->shadowProperties[$property_key];
$this->setShadowPropertiesMapValues($property_val, $reference);
}
} else {
if ($base_reference === null) {
$this->shadowProperties[$property_key] = $property_val;
} else {
$reference[$property_key] = $property_val;
}
}
}
return $this;
}
/**
* Set Shadow Blur.
*
* @return $this
*/
protected function setShadowBlur(?float $blur)
{
if ($blur !== null) {
$this->shadowProperties['blur'] = $blur;
}
return $this;
}
/**
* Set Shadow Angle.
*
* @return $this
*/
protected function setShadowAngle(null|float|int|string $angle)
{
if (is_numeric($angle)) {
$this->shadowProperties['direction'] = $angle;
}
return $this;
}
/**
* Set Shadow Distance.
*
* @return $this
*/
protected function setShadowDistance(?float $distance)
{
if ($distance !== null) {
$this->shadowProperties['distance'] = $distance;
}
return $this;
}
public function getShadowColorObject(): ChartColor
{
return $this->shadowColor;
}
/**
* Get Shadow Property.
*
* @param string|string[] $elements
*/
public function getShadowProperty($elements): array|string|null
{
if ($elements === 'color') {
return [
'value' => $this->shadowColor->getValue(),
'type' => $this->shadowColor->getType(),
'alpha' => $this->shadowColor->getAlpha(),
];
}
return $this->getArrayElementsValue($this->shadowProperties, $elements);
}
public function getShadowArray(): array
{
$array = $this->shadowProperties;
if ($this->getShadowColorObject()->isUsable()) {
$array['color'] = $this->getShadowProperty('color');
}
return $array;
}
protected ChartColor $lineColor;
protected array $lineStyleProperties = [
'width' => null, //'9525',
'compound' => '', //self::LINE_STYLE_COMPOUND_SIMPLE,
'dash' => '', //self::LINE_STYLE_DASH_SOLID,
'cap' => '', //self::LINE_STYLE_CAP_FLAT,
'join' => '', //self::LINE_STYLE_JOIN_BEVEL,
'arrow' => [
'head' => [
'type' => '', //self::LINE_STYLE_ARROW_TYPE_NOARROW,
'size' => '', //self::LINE_STYLE_ARROW_SIZE_5,
'w' => '',
'len' => '',
],
'end' => [
'type' => '', //self::LINE_STYLE_ARROW_TYPE_NOARROW,
'size' => '', //self::LINE_STYLE_ARROW_SIZE_8,
'w' => '',
'len' => '',
],
],
];
public function copyLineStyles(self $otherProperties): void
{
$this->lineStyleProperties = $otherProperties->lineStyleProperties;
$this->lineColor = $otherProperties->lineColor;
$this->glowSize = $otherProperties->glowSize;
$this->glowColor = $otherProperties->glowColor;
$this->softEdges = $otherProperties->softEdges;
$this->shadowProperties = $otherProperties->shadowProperties;
}
public function getLineColor(): ChartColor
{
return $this->lineColor;
}
/**
* Set Line Color Properties.
*/
public function setLineColorProperties(?string $value, ?int $alpha = null, ?string $colorType = null): void
{
$this->activateObject();
$this->lineColor->setColorPropertiesArray(
$this->setColorProperties(
$value,
$alpha,
$colorType
)
);
}
/**
* Get Line Color Property.
*/
public function getLineColorProperty(string $propertyName): null|int|string
{
return $this->lineColor->getColorProperty($propertyName);
}
/**
* Set Line Style Properties.
*/
public function setLineStyleProperties(
null|float|int|string $lineWidth = null,
?string $compoundType = '',
?string $dashType = '',
?string $capType = '',
?string $joinType = '',
?string $headArrowType = '',
int $headArrowSize = 0,
?string $endArrowType = '',
int $endArrowSize = 0,
?string $headArrowWidth = '',
?string $headArrowLength = '',
?string $endArrowWidth = '',
?string $endArrowLength = ''
): void {
$this->activateObject();
if (is_numeric($lineWidth)) {
$this->lineStyleProperties['width'] = $lineWidth;
}
if ($compoundType !== '') {
$this->lineStyleProperties['compound'] = $compoundType;
}
if ($dashType !== '') {
$this->lineStyleProperties['dash'] = $dashType;
}
if ($capType !== '') {
$this->lineStyleProperties['cap'] = $capType;
}
if ($joinType !== '') {
$this->lineStyleProperties['join'] = $joinType;
}
if ($headArrowType !== '') {
$this->lineStyleProperties['arrow']['head']['type'] = $headArrowType;
}
if (isset(self::ARROW_SIZES[$headArrowSize])) {
$this->lineStyleProperties['arrow']['head']['size'] = $headArrowSize;
$this->lineStyleProperties['arrow']['head']['w'] = self::ARROW_SIZES[$headArrowSize]['w'];
$this->lineStyleProperties['arrow']['head']['len'] = self::ARROW_SIZES[$headArrowSize]['len'];
}
if ($endArrowType !== '') {
$this->lineStyleProperties['arrow']['end']['type'] = $endArrowType;
}
if (isset(self::ARROW_SIZES[$endArrowSize])) {
$this->lineStyleProperties['arrow']['end']['size'] = $endArrowSize;
$this->lineStyleProperties['arrow']['end']['w'] = self::ARROW_SIZES[$endArrowSize]['w'];
$this->lineStyleProperties['arrow']['end']['len'] = self::ARROW_SIZES[$endArrowSize]['len'];
}
if ($headArrowWidth !== '') {
$this->lineStyleProperties['arrow']['head']['w'] = $headArrowWidth;
}
if ($headArrowLength !== '') {
$this->lineStyleProperties['arrow']['head']['len'] = $headArrowLength;
}
if ($endArrowWidth !== '') {
$this->lineStyleProperties['arrow']['end']['w'] = $endArrowWidth;
}
if ($endArrowLength !== '') {
$this->lineStyleProperties['arrow']['end']['len'] = $endArrowLength;
}
}
public function getLineStyleArray(): array
{
return $this->lineStyleProperties;
}
public function setLineStyleArray(array $lineStyleProperties = []): self
{
$this->activateObject();
$this->lineStyleProperties['width'] = $lineStyleProperties['width'] ?? null;
$this->lineStyleProperties['compound'] = $lineStyleProperties['compound'] ?? '';
$this->lineStyleProperties['dash'] = $lineStyleProperties['dash'] ?? '';
$this->lineStyleProperties['cap'] = $lineStyleProperties['cap'] ?? '';
$this->lineStyleProperties['join'] = $lineStyleProperties['join'] ?? '';
$this->lineStyleProperties['arrow']['head']['type'] = $lineStyleProperties['arrow']['head']['type'] ?? '';
$this->lineStyleProperties['arrow']['head']['size'] = $lineStyleProperties['arrow']['head']['size'] ?? '';
$this->lineStyleProperties['arrow']['head']['w'] = $lineStyleProperties['arrow']['head']['w'] ?? '';
$this->lineStyleProperties['arrow']['head']['len'] = $lineStyleProperties['arrow']['head']['len'] ?? '';
$this->lineStyleProperties['arrow']['end']['type'] = $lineStyleProperties['arrow']['end']['type'] ?? '';
$this->lineStyleProperties['arrow']['end']['size'] = $lineStyleProperties['arrow']['end']['size'] ?? '';
$this->lineStyleProperties['arrow']['end']['w'] = $lineStyleProperties['arrow']['end']['w'] ?? '';
$this->lineStyleProperties['arrow']['end']['len'] = $lineStyleProperties['arrow']['end']['len'] ?? '';
return $this;
}
public function setLineStyleProperty(string $propertyName, mixed $value): self
{
$this->activateObject();
$this->lineStyleProperties[$propertyName] = $value;
return $this;
}
/**
* Get Line Style Property.
*/
public function getLineStyleProperty(array|string $elements): ?string
{
return $this->getArrayElementsValue($this->lineStyleProperties, $elements);
}
protected const ARROW_SIZES = [
1 => ['w' => 'sm', 'len' => 'sm'],
2 => ['w' => 'sm', 'len' => 'med'],
3 => ['w' => 'sm', 'len' => 'lg'],
4 => ['w' => 'med', 'len' => 'sm'],
5 => ['w' => 'med', 'len' => 'med'],
6 => ['w' => 'med', 'len' => 'lg'],
7 => ['w' => 'lg', 'len' => 'sm'],
8 => ['w' => 'lg', 'len' => 'med'],
9 => ['w' => 'lg', 'len' => 'lg'],
];
/**
* Get Line Style Arrow Size.
*/
protected function getLineStyleArrowSize(int $arraySelector, string $arrayKaySelector): string
{
return self::ARROW_SIZES[$arraySelector][$arrayKaySelector] ?? '';
}
/**
* Get Line Style Arrow Parameters.
*/
public function getLineStyleArrowParameters(string $arrowSelector, string $propertySelector): string
{
return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrowSelector]['size'], $propertySelector);
}
/**
* Get Line Style Arrow Width.
*/
public function getLineStyleArrowWidth(string $arrow): ?string
{
return $this->getLineStyleProperty(['arrow', $arrow, 'w']);
}
/**
* Get Line Style Arrow Excel Length.
*/
public function getLineStyleArrowLength(string $arrow): ?string
{
return $this->getLineStyleProperty(['arrow', $arrow, 'len']);
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$this->lineColor = clone $this->lineColor;
$this->glowColor = clone $this->glowColor;
$this->shadowColor = clone $this->shadowColor;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php 0000644 00000037774 15167673464 0016151 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Chart
{
/**
* Chart Name.
*/
private string $name;
/**
* Worksheet.
*/
private ?Worksheet $worksheet = null;
/**
* Chart Title.
*/
private ?Title $title;
/**
* Chart Legend.
*/
private ?Legend $legend;
/**
* X-Axis Label.
*/
private ?Title $xAxisLabel;
/**
* Y-Axis Label.
*/
private ?Title $yAxisLabel;
/**
* Chart Plot Area.
*/
private ?PlotArea $plotArea;
/**
* Plot Visible Only.
*/
private bool $plotVisibleOnly;
/**
* Display Blanks as.
*/
private string $displayBlanksAs;
/**
* Chart Asix Y as.
*/
private Axis $yAxis;
/**
* Chart Asix X as.
*/
private Axis $xAxis;
/**
* Top-Left Cell Position.
*/
private string $topLeftCellRef = 'A1';
/**
* Top-Left X-Offset.
*/
private int $topLeftXOffset = 0;
/**
* Top-Left Y-Offset.
*/
private int $topLeftYOffset = 0;
/**
* Bottom-Right Cell Position.
*/
private string $bottomRightCellRef = '';
/**
* Bottom-Right X-Offset.
*/
private int $bottomRightXOffset = 10;
/**
* Bottom-Right Y-Offset.
*/
private int $bottomRightYOffset = 10;
private ?int $rotX = null;
private ?int $rotY = null;
private ?int $rAngAx = null;
private ?int $perspective = null;
private bool $oneCellAnchor = false;
private bool $autoTitleDeleted = false;
private bool $noFill = false;
private bool $roundedCorners = false;
private GridLines $borderLines;
private ChartColor $fillColor;
/**
* Rendered width in pixels.
*/
private ?float $renderedWidth = null;
/**
* Rendered height in pixels.
*/
private ?float $renderedHeight = null;
/**
* Create a new Chart.
* majorGridlines and minorGridlines are deprecated, moved to Axis.
*/
public function __construct(string $name, ?Title $title = null, ?Legend $legend = null, ?PlotArea $plotArea = null, bool $plotVisibleOnly = true, string $displayBlanksAs = DataSeries::EMPTY_AS_GAP, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null, ?GridLines $majorGridlines = null, ?GridLines $minorGridlines = null)
{
$this->name = $name;
$this->title = $title;
$this->legend = $legend;
$this->xAxisLabel = $xAxisLabel;
$this->yAxisLabel = $yAxisLabel;
$this->plotArea = $plotArea;
$this->plotVisibleOnly = $plotVisibleOnly;
$this->displayBlanksAs = $displayBlanksAs;
$this->xAxis = $xAxis ?? new Axis();
$this->yAxis = $yAxis ?? new Axis();
if ($majorGridlines !== null) {
$this->yAxis->setMajorGridlines($majorGridlines);
}
if ($minorGridlines !== null) {
$this->yAxis->setMinorGridlines($minorGridlines);
}
$this->fillColor = new ChartColor();
$this->borderLines = new GridLines();
}
public function __destruct()
{
$this->worksheet = null;
}
/**
* Get Name.
*/
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* Get Worksheet.
*/
public function getWorksheet(): ?Worksheet
{
return $this->worksheet;
}
/**
* Set Worksheet.
*
* @return $this
*/
public function setWorksheet(?Worksheet $worksheet = null): static
{
$this->worksheet = $worksheet;
return $this;
}
public function getTitle(): ?Title
{
return $this->title;
}
/**
* Set Title.
*
* @return $this
*/
public function setTitle(Title $title): static
{
$this->title = $title;
return $this;
}
public function getLegend(): ?Legend
{
return $this->legend;
}
/**
* Set Legend.
*
* @return $this
*/
public function setLegend(Legend $legend): static
{
$this->legend = $legend;
return $this;
}
public function getXAxisLabel(): ?Title
{
return $this->xAxisLabel;
}
/**
* Set X-Axis Label.
*
* @return $this
*/
public function setXAxisLabel(Title $label): static
{
$this->xAxisLabel = $label;
return $this;
}
public function getYAxisLabel(): ?Title
{
return $this->yAxisLabel;
}
/**
* Set Y-Axis Label.
*
* @return $this
*/
public function setYAxisLabel(Title $label): static
{
$this->yAxisLabel = $label;
return $this;
}
public function getPlotArea(): ?PlotArea
{
return $this->plotArea;
}
public function getPlotAreaOrThrow(): PlotArea
{
$plotArea = $this->getPlotArea();
if ($plotArea !== null) {
return $plotArea;
}
throw new Exception('Chart has no PlotArea');
}
/**
* Set Plot Area.
*/
public function setPlotArea(PlotArea $plotArea): self
{
$this->plotArea = $plotArea;
return $this;
}
/**
* Get Plot Visible Only.
*/
public function getPlotVisibleOnly(): bool
{
return $this->plotVisibleOnly;
}
/**
* Set Plot Visible Only.
*
* @return $this
*/
public function setPlotVisibleOnly(bool $plotVisibleOnly): static
{
$this->plotVisibleOnly = $plotVisibleOnly;
return $this;
}
/**
* Get Display Blanks as.
*/
public function getDisplayBlanksAs(): string
{
return $this->displayBlanksAs;
}
/**
* Set Display Blanks as.
*
* @return $this
*/
public function setDisplayBlanksAs(string $displayBlanksAs): static
{
$this->displayBlanksAs = $displayBlanksAs;
return $this;
}
public function getChartAxisY(): Axis
{
return $this->yAxis;
}
/**
* Set yAxis.
*/
public function setChartAxisY(?Axis $axis): self
{
$this->yAxis = $axis ?? new Axis();
return $this;
}
public function getChartAxisX(): Axis
{
return $this->xAxis;
}
/**
* Set xAxis.
*/
public function setChartAxisX(?Axis $axis): self
{
$this->xAxis = $axis ?? new Axis();
return $this;
}
/**
* Set the Top Left position for the chart.
*
* @return $this
*/
public function setTopLeftPosition(string $cellAddress, ?int $xOffset = null, ?int $yOffset = null): static
{
$this->topLeftCellRef = $cellAddress;
if ($xOffset !== null) {
$this->setTopLeftXOffset($xOffset);
}
if ($yOffset !== null) {
$this->setTopLeftYOffset($yOffset);
}
return $this;
}
/**
* Get the top left position of the chart.
*
* Returns ['cell' => string cell address, 'xOffset' => int, 'yOffset' => int].
*
* @return array{cell: string, xOffset: int, yOffset: int} an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell
*/
public function getTopLeftPosition(): array
{
return [
'cell' => $this->topLeftCellRef,
'xOffset' => $this->topLeftXOffset,
'yOffset' => $this->topLeftYOffset,
];
}
/**
* Get the cell address where the top left of the chart is fixed.
*/
public function getTopLeftCell(): string
{
return $this->topLeftCellRef;
}
/**
* Set the Top Left cell position for the chart.
*
* @return $this
*/
public function setTopLeftCell(string $cellAddress): static
{
$this->topLeftCellRef = $cellAddress;
return $this;
}
/**
* Set the offset position within the Top Left cell for the chart.
*
* @return $this
*/
public function setTopLeftOffset(?int $xOffset, ?int $yOffset): static
{
if ($xOffset !== null) {
$this->setTopLeftXOffset($xOffset);
}
if ($yOffset !== null) {
$this->setTopLeftYOffset($yOffset);
}
return $this;
}
/**
* Get the offset position within the Top Left cell for the chart.
*
* @return int[]
*/
public function getTopLeftOffset(): array
{
return [
'X' => $this->topLeftXOffset,
'Y' => $this->topLeftYOffset,
];
}
/**
* @return $this
*/
public function setTopLeftXOffset(int $xOffset): static
{
$this->topLeftXOffset = $xOffset;
return $this;
}
public function getTopLeftXOffset(): int
{
return $this->topLeftXOffset;
}
/**
* @return $this
*/
public function setTopLeftYOffset(int $yOffset): static
{
$this->topLeftYOffset = $yOffset;
return $this;
}
public function getTopLeftYOffset(): int
{
return $this->topLeftYOffset;
}
/**
* Set the Bottom Right position of the chart.
*
* @return $this
*/
public function setBottomRightPosition(string $cellAddress = '', ?int $xOffset = null, ?int $yOffset = null): static
{
$this->bottomRightCellRef = $cellAddress;
if ($xOffset !== null) {
$this->setBottomRightXOffset($xOffset);
}
if ($yOffset !== null) {
$this->setBottomRightYOffset($yOffset);
}
return $this;
}
/**
* Get the bottom right position of the chart.
*
* @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell
*/
public function getBottomRightPosition(): array
{
return [
'cell' => $this->bottomRightCellRef,
'xOffset' => $this->bottomRightXOffset,
'yOffset' => $this->bottomRightYOffset,
];
}
/**
* Set the Bottom Right cell for the chart.
*
* @return $this
*/
public function setBottomRightCell(string $cellAddress = ''): static
{
$this->bottomRightCellRef = $cellAddress;
return $this;
}
/**
* Get the cell address where the bottom right of the chart is fixed.
*/
public function getBottomRightCell(): string
{
return $this->bottomRightCellRef;
}
/**
* Set the offset position within the Bottom Right cell for the chart.
*
* @return $this
*/
public function setBottomRightOffset(?int $xOffset, ?int $yOffset): static
{
if ($xOffset !== null) {
$this->setBottomRightXOffset($xOffset);
}
if ($yOffset !== null) {
$this->setBottomRightYOffset($yOffset);
}
return $this;
}
/**
* Get the offset position within the Bottom Right cell for the chart.
*
* @return int[]
*/
public function getBottomRightOffset(): array
{
return [
'X' => $this->bottomRightXOffset,
'Y' => $this->bottomRightYOffset,
];
}
/**
* @return $this
*/
public function setBottomRightXOffset(int $xOffset): static
{
$this->bottomRightXOffset = $xOffset;
return $this;
}
public function getBottomRightXOffset(): int
{
return $this->bottomRightXOffset;
}
/**
* @return $this
*/
public function setBottomRightYOffset(int $yOffset): static
{
$this->bottomRightYOffset = $yOffset;
return $this;
}
public function getBottomRightYOffset(): int
{
return $this->bottomRightYOffset;
}
public function refresh(): void
{
if ($this->worksheet !== null && $this->plotArea !== null) {
$this->plotArea->refresh($this->worksheet);
}
}
/**
* Render the chart to given file (or stream).
*
* @param ?string $outputDestination Name of the file render to
*
* @return bool true on success
*/
public function render(?string $outputDestination = null): bool
{
if ($outputDestination == 'php://output') {
$outputDestination = null;
}
$libraryName = Settings::getChartRenderer();
if ($libraryName === null) {
return false;
}
// Ensure that data series values are up-to-date before we render
$this->refresh();
$renderer = new $libraryName($this);
return $renderer->render($outputDestination);
}
public function getRotX(): ?int
{
return $this->rotX;
}
public function setRotX(?int $rotX): self
{
$this->rotX = $rotX;
return $this;
}
public function getRotY(): ?int
{
return $this->rotY;
}
public function setRotY(?int $rotY): self
{
$this->rotY = $rotY;
return $this;
}
public function getRAngAx(): ?int
{
return $this->rAngAx;
}
public function setRAngAx(?int $rAngAx): self
{
$this->rAngAx = $rAngAx;
return $this;
}
public function getPerspective(): ?int
{
return $this->perspective;
}
public function setPerspective(?int $perspective): self
{
$this->perspective = $perspective;
return $this;
}
public function getOneCellAnchor(): bool
{
return $this->oneCellAnchor;
}
public function setOneCellAnchor(bool $oneCellAnchor): self
{
$this->oneCellAnchor = $oneCellAnchor;
return $this;
}
public function getAutoTitleDeleted(): bool
{
return $this->autoTitleDeleted;
}
public function setAutoTitleDeleted(bool $autoTitleDeleted): self
{
$this->autoTitleDeleted = $autoTitleDeleted;
return $this;
}
public function getNoFill(): bool
{
return $this->noFill;
}
public function setNoFill(bool $noFill): self
{
$this->noFill = $noFill;
return $this;
}
public function getRoundedCorners(): bool
{
return $this->roundedCorners;
}
public function setRoundedCorners(?bool $roundedCorners): self
{
if ($roundedCorners !== null) {
$this->roundedCorners = $roundedCorners;
}
return $this;
}
public function getBorderLines(): GridLines
{
return $this->borderLines;
}
public function setBorderLines(GridLines $borderLines): self
{
$this->borderLines = $borderLines;
return $this;
}
public function getFillColor(): ChartColor
{
return $this->fillColor;
}
public function setRenderedWidth(?float $width): self
{
$this->renderedWidth = $width;
return $this;
}
public function getRenderedWidth(): ?float
{
return $this->renderedWidth;
}
public function setRenderedHeight(?float $height): self
{
$this->renderedHeight = $height;
return $this;
}
public function getRenderedHeight(): ?float
{
return $this->renderedHeight;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$this->worksheet = null;
$this->title = ($this->title === null) ? null : clone $this->title;
$this->legend = ($this->legend === null) ? null : clone $this->legend;
$this->xAxisLabel = ($this->xAxisLabel === null) ? null : clone $this->xAxisLabel;
$this->yAxisLabel = ($this->yAxisLabel === null) ? null : clone $this->yAxisLabel;
$this->plotArea = ($this->plotArea === null) ? null : clone $this->plotArea;
$this->xAxis = clone $this->xAxis;
$this->yAxis = clone $this->yAxis;
$this->borderLines = clone $this->borderLines;
$this->fillColor = clone $this->fillColor;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php 0000644 00000010134 15167673464 0016574 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class PlotArea
{
/**
* No fill in plot area (show Excel gridlines through chart).
*/
private bool $noFill = false;
/**
* PlotArea Gradient Stop list.
* Each entry is a 2-element array.
* First is position in %.
* Second is ChartColor.
*
* @var array[]
*/
private array $gradientFillStops = [];
/**
* PlotArea Gradient Angle.
*/
private ?float $gradientFillAngle = null;
/**
* PlotArea Layout.
*/
private ?Layout $layout;
/**
* Plot Series.
*
* @var DataSeries[]
*/
private array $plotSeries;
/**
* Create a new PlotArea.
*
* @param DataSeries[] $plotSeries
*/
public function __construct(?Layout $layout = null, array $plotSeries = [])
{
$this->layout = $layout;
$this->plotSeries = $plotSeries;
}
public function getLayout(): ?Layout
{
return $this->layout;
}
/**
* Get Number of Plot Groups.
*/
public function getPlotGroupCount(): int
{
return count($this->plotSeries);
}
/**
* Get Number of Plot Series.
*/
public function getPlotSeriesCount(): int|float
{
$seriesCount = 0;
foreach ($this->plotSeries as $plot) {
$seriesCount += $plot->getPlotSeriesCount();
}
return $seriesCount;
}
/**
* Get Plot Series.
*
* @return DataSeries[]
*/
public function getPlotGroup(): array
{
return $this->plotSeries;
}
/**
* Get Plot Series by Index.
*/
public function getPlotGroupByIndex(int $index): DataSeries
{
return $this->plotSeries[$index];
}
/**
* Set Plot Series.
*
* @param DataSeries[] $plotSeries
*
* @return $this
*/
public function setPlotSeries(array $plotSeries): static
{
$this->plotSeries = $plotSeries;
return $this;
}
public function refresh(Worksheet $worksheet): void
{
foreach ($this->plotSeries as $plotSeries) {
$plotSeries->refresh($worksheet);
}
}
public function setNoFill(bool $noFill): self
{
$this->noFill = $noFill;
return $this;
}
public function getNoFill(): bool
{
return $this->noFill;
}
public function setGradientFillProperties(array $gradientFillStops, ?float $gradientFillAngle): self
{
$this->gradientFillStops = $gradientFillStops;
$this->gradientFillAngle = $gradientFillAngle;
return $this;
}
/**
* Get gradientFillAngle.
*/
public function getGradientFillAngle(): ?float
{
return $this->gradientFillAngle;
}
/**
* Get gradientFillStops.
*/
public function getGradientFillStops(): array
{
return $this->gradientFillStops;
}
private ?int $gapWidth = null;
private bool $useUpBars = false;
private bool $useDownBars = false;
public function getGapWidth(): ?int
{
return $this->gapWidth;
}
public function setGapWidth(?int $gapWidth): self
{
$this->gapWidth = $gapWidth;
return $this;
}
public function getUseUpBars(): bool
{
return $this->useUpBars;
}
public function setUseUpBars(bool $useUpBars): self
{
$this->useUpBars = $useUpBars;
return $this;
}
public function getUseDownBars(): bool
{
return $this->useDownBars;
}
public function setUseDownBars(bool $useDownBars): self
{
$this->useDownBars = $useDownBars;
return $this;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$this->layout = ($this->layout === null) ? null : clone $this->layout;
$plotSeries = $this->plotSeries;
$this->plotSeries = [];
foreach ($plotSeries as $series) {
$this->plotSeries[] = clone $series;
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php 0000644 00000010416 15167673464 0016266 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
class Legend
{
/** Legend positions */
const XL_LEGEND_POSITION_BOTTOM = -4107; // Below the chart.
const XL_LEGEND_POSITION_CORNER = 2; // In the upper right-hand corner of the chart border.
const XL_LEGEND_POSITION_CUSTOM = -4161; // A custom position.
const XL_LEGEND_POSITION_LEFT = -4131; // Left of the chart.
const XL_LEGEND_POSITION_RIGHT = -4152; // Right of the chart.
const XL_LEGEND_POSITION_TOP = -4160; // Above the chart.
const POSITION_RIGHT = 'r';
const POSITION_LEFT = 'l';
const POSITION_BOTTOM = 'b';
const POSITION_TOP = 't';
const POSITION_TOPRIGHT = 'tr';
const POSITION_XLREF = [
self::XL_LEGEND_POSITION_BOTTOM => self::POSITION_BOTTOM,
self::XL_LEGEND_POSITION_CORNER => self::POSITION_TOPRIGHT,
self::XL_LEGEND_POSITION_CUSTOM => '??',
self::XL_LEGEND_POSITION_LEFT => self::POSITION_LEFT,
self::XL_LEGEND_POSITION_RIGHT => self::POSITION_RIGHT,
self::XL_LEGEND_POSITION_TOP => self::POSITION_TOP,
];
/**
* Legend position.
*/
private string $position = self::POSITION_RIGHT;
/**
* Allow overlay of other elements?
*/
private bool $overlay = true;
/**
* Legend Layout.
*/
private ?Layout $layout;
private GridLines $borderLines;
private ChartColor $fillColor;
private ?AxisText $legendText = null;
/**
* Create a new Legend.
*/
public function __construct(string $position = self::POSITION_RIGHT, ?Layout $layout = null, bool $overlay = false)
{
$this->setPosition($position);
$this->layout = $layout;
$this->setOverlay($overlay);
$this->borderLines = new GridLines();
$this->fillColor = new ChartColor();
}
public function getFillColor(): ChartColor
{
return $this->fillColor;
}
/**
* Get legend position as an excel string value.
*/
public function getPosition(): string
{
return $this->position;
}
/**
* Get legend position using an excel string value.
*
* @param string $position see self::POSITION_*
*/
public function setPosition(string $position): bool
{
if (!in_array($position, self::POSITION_XLREF)) {
return false;
}
$this->position = $position;
return true;
}
/**
* Get legend position as an Excel internal numeric value.
*/
public function getPositionXL(): false|int
{
return array_search($this->position, self::POSITION_XLREF);
}
/**
* Set legend position using an Excel internal numeric value.
*
* @param int $positionXL see self::XL_LEGEND_POSITION_*
*/
public function setPositionXL(int $positionXL): bool
{
if (!isset(self::POSITION_XLREF[$positionXL])) {
return false;
}
$this->position = self::POSITION_XLREF[$positionXL];
return true;
}
/**
* Get allow overlay of other elements?
*/
public function getOverlay(): bool
{
return $this->overlay;
}
/**
* Set allow overlay of other elements?
*/
public function setOverlay(bool $overlay): void
{
$this->overlay = $overlay;
}
/**
* Get Layout.
*/
public function getLayout(): ?Layout
{
return $this->layout;
}
public function getLegendText(): ?AxisText
{
return $this->legendText;
}
public function setLegendText(?AxisText $legendText): self
{
$this->legendText = $legendText;
return $this;
}
public function getBorderLines(): GridLines
{
return $this->borderLines;
}
public function setBorderLines(GridLines $borderLines): self
{
$this->borderLines = $borderLines;
return $this;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$this->layout = ($this->layout === null) ? null : clone $this->layout;
$this->legendText = ($this->legendText === null) ? null : clone $this->legendText;
$this->borderLines = clone $this->borderLines;
$this->fillColor = clone $this->fillColor;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php 0000644 00000000252 15167673464 0017023 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class Exception extends PhpSpreadsheetException
{
}
phpspreadsheet/src/PhpSpreadsheet/Chart/ChartColor.php 0000644 00000007601 15167673464 0017132 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
class ChartColor
{
const EXCEL_COLOR_TYPE_STANDARD = 'prstClr';
const EXCEL_COLOR_TYPE_SCHEME = 'schemeClr';
const EXCEL_COLOR_TYPE_RGB = 'srgbClr';
const EXCEL_COLOR_TYPES = [
self::EXCEL_COLOR_TYPE_RGB,
self::EXCEL_COLOR_TYPE_SCHEME,
self::EXCEL_COLOR_TYPE_STANDARD,
];
private string $value = '';
private string $type = '';
private ?int $alpha = null;
private ?int $brightness = null;
/**
* @param string|string[] $value
*/
public function __construct($value = '', ?int $alpha = null, ?string $type = null, ?int $brightness = null)
{
if (is_array($value)) {
$this->setColorPropertiesArray($value);
} else {
$this->setColorProperties($value, $alpha, $type, $brightness);
}
}
public function getValue(): string
{
return $this->value;
}
public function setValue(string $value): self
{
$this->value = $value;
return $this;
}
public function getType(): string
{
return $this->type;
}
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
public function getAlpha(): ?int
{
return $this->alpha;
}
public function setAlpha(?int $alpha): self
{
$this->alpha = $alpha;
return $this;
}
public function getBrightness(): ?int
{
return $this->brightness;
}
public function setBrightness(?int $brightness): self
{
$this->brightness = $brightness;
return $this;
}
public function setColorProperties(?string $color, null|float|int|string $alpha = null, ?string $type = null, null|float|int|string $brightness = null): self
{
if (empty($type) && !empty($color)) {
if (str_starts_with($color, '*')) {
$type = 'schemeClr';
$color = substr($color, 1);
} elseif (str_starts_with($color, '/')) {
$type = 'prstClr';
$color = substr($color, 1);
} elseif (preg_match('/^[0-9A-Fa-f]{6}$/', $color) === 1) {
$type = 'srgbClr';
}
}
if ($color !== null) {
$this->setValue("$color");
}
if ($type !== null) {
$this->setType($type);
}
if ($alpha === null) {
$this->setAlpha(null);
} elseif (is_numeric($alpha)) {
$this->setAlpha((int) $alpha);
}
if ($brightness === null) {
$this->setBrightness(null);
} elseif (is_numeric($brightness)) {
$this->setBrightness((int) $brightness);
}
return $this;
}
public function setColorPropertiesArray(array $color): self
{
return $this->setColorProperties(
$color['value'] ?? '',
$color['alpha'] ?? null,
$color['type'] ?? null,
$color['brightness'] ?? null
);
}
public function isUsable(): bool
{
return $this->type !== '' && $this->value !== '';
}
/**
* Get Color Property.
*/
public function getColorProperty(string $propertyName): null|int|string
{
$retVal = null;
if ($propertyName === 'value') {
$retVal = $this->value;
} elseif ($propertyName === 'type') {
$retVal = $this->type;
} elseif ($propertyName === 'alpha') {
$retVal = $this->alpha;
} elseif ($propertyName === 'brightness') {
$retVal = $this->brightness;
}
return $retVal;
}
public static function alphaToXml(int $alpha): string
{
return (string) (100 - $alpha) . '000';
}
public static function alphaFromXml(float|int|string $alpha): int
{
return 100 - ((int) $alpha / 1000);
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php 0000644 00000022444 15167673464 0017120 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class DataSeries
{
const TYPE_BARCHART = 'barChart';
const TYPE_BARCHART_3D = 'bar3DChart';
const TYPE_LINECHART = 'lineChart';
const TYPE_LINECHART_3D = 'line3DChart';
const TYPE_AREACHART = 'areaChart';
const TYPE_AREACHART_3D = 'area3DChart';
const TYPE_PIECHART = 'pieChart';
const TYPE_PIECHART_3D = 'pie3DChart';
const TYPE_DOUGHNUTCHART = 'doughnutChart';
const TYPE_DONUTCHART = self::TYPE_DOUGHNUTCHART; // Synonym
const TYPE_SCATTERCHART = 'scatterChart';
const TYPE_SURFACECHART = 'surfaceChart';
const TYPE_SURFACECHART_3D = 'surface3DChart';
const TYPE_RADARCHART = 'radarChart';
const TYPE_BUBBLECHART = 'bubbleChart';
const TYPE_STOCKCHART = 'stockChart';
const TYPE_CANDLECHART = self::TYPE_STOCKCHART; // Synonym
const GROUPING_CLUSTERED = 'clustered';
const GROUPING_STACKED = 'stacked';
const GROUPING_PERCENT_STACKED = 'percentStacked';
const GROUPING_STANDARD = 'standard';
const DIRECTION_BAR = 'bar';
const DIRECTION_HORIZONTAL = self::DIRECTION_BAR;
const DIRECTION_COL = 'col';
const DIRECTION_COLUMN = self::DIRECTION_COL;
const DIRECTION_VERTICAL = self::DIRECTION_COL;
const STYLE_LINEMARKER = 'lineMarker';
const STYLE_SMOOTHMARKER = 'smoothMarker';
const STYLE_MARKER = 'marker';
const STYLE_FILLED = 'filled';
const EMPTY_AS_GAP = 'gap';
const EMPTY_AS_ZERO = 'zero';
const EMPTY_AS_SPAN = 'span';
/**
* Series Plot Type.
*/
private ?string $plotType;
/**
* Plot Grouping Type.
*/
private ?string $plotGrouping;
/**
* Plot Direction.
*/
private string $plotDirection;
/**
* Plot Style.
*/
private ?string $plotStyle;
/**
* Order of plots in Series.
*
* @var int[]
*/
private array $plotOrder;
/**
* Plot Label.
*
* @var DataSeriesValues[]
*/
private array $plotLabel;
/**
* Plot Category.
*
* @var DataSeriesValues[]
*/
private array $plotCategory;
/**
* Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
*/
private bool $smoothLine;
/**
* Plot Values.
*
* @var DataSeriesValues[]
*/
private array $plotValues;
/**
* Plot Bubble Sizes.
*
* @var DataSeriesValues[]
*/
private array $plotBubbleSizes = [];
/**
* Create a new DataSeries.
*
* @param int[] $plotOrder
* @param DataSeriesValues[] $plotLabel
* @param DataSeriesValues[] $plotCategory
* @param DataSeriesValues[] $plotValues
*/
public function __construct(
null|string $plotType = null,
null|string $plotGrouping = null,
array $plotOrder = [],
array $plotLabel = [],
array $plotCategory = [],
array $plotValues = [],
?string $plotDirection = null,
bool $smoothLine = false,
?string $plotStyle = null
) {
$this->plotType = $plotType;
$this->plotGrouping = $plotGrouping;
$this->plotOrder = $plotOrder;
$keys = array_keys($plotValues);
$this->plotValues = $plotValues;
if (!isset($plotLabel[$keys[0]])) {
$plotLabel[$keys[0]] = new DataSeriesValues();
}
$this->plotLabel = $plotLabel;
if (!isset($plotCategory[$keys[0]])) {
$plotCategory[$keys[0]] = new DataSeriesValues();
}
$this->plotCategory = $plotCategory;
$this->smoothLine = (bool) $smoothLine;
$this->plotStyle = $plotStyle;
if ($plotDirection === null) {
$plotDirection = self::DIRECTION_COL;
}
$this->plotDirection = $plotDirection;
}
/**
* Get Plot Type.
*/
public function getPlotType(): ?string
{
return $this->plotType;
}
/**
* Set Plot Type.
*
* @return $this
*/
public function setPlotType(string $plotType): static
{
$this->plotType = $plotType;
return $this;
}
/**
* Get Plot Grouping Type.
*/
public function getPlotGrouping(): ?string
{
return $this->plotGrouping;
}
/**
* Set Plot Grouping Type.
*
* @return $this
*/
public function setPlotGrouping(string $groupingType): static
{
$this->plotGrouping = $groupingType;
return $this;
}
/**
* Get Plot Direction.
*/
public function getPlotDirection(): string
{
return $this->plotDirection;
}
/**
* Set Plot Direction.
*
* @return $this
*/
public function setPlotDirection(string $plotDirection): static
{
$this->plotDirection = $plotDirection;
return $this;
}
/**
* Get Plot Order.
*
* @return int[]
*/
public function getPlotOrder(): array
{
return $this->plotOrder;
}
/**
* Get Plot Labels.
*
* @return DataSeriesValues[]
*/
public function getPlotLabels(): array
{
return $this->plotLabel;
}
/**
* Get Plot Label by Index.
*
* @return DataSeriesValues|false
*/
public function getPlotLabelByIndex(int $index): bool|DataSeriesValues
{
$keys = array_keys($this->plotLabel);
if (in_array($index, $keys)) {
return $this->plotLabel[$index];
}
return false;
}
/**
* Get Plot Categories.
*
* @return DataSeriesValues[]
*/
public function getPlotCategories(): array
{
return $this->plotCategory;
}
/**
* Get Plot Category by Index.
*
* @return DataSeriesValues|false
*/
public function getPlotCategoryByIndex(int $index): bool|DataSeriesValues
{
$keys = array_keys($this->plotCategory);
if (in_array($index, $keys)) {
return $this->plotCategory[$index];
} elseif (isset($keys[$index])) {
return $this->plotCategory[$keys[$index]];
}
return false;
}
/**
* Get Plot Style.
*/
public function getPlotStyle(): ?string
{
return $this->plotStyle;
}
/**
* Set Plot Style.
*
* @return $this
*/
public function setPlotStyle(?string $plotStyle): static
{
$this->plotStyle = $plotStyle;
return $this;
}
/**
* Get Plot Values.
*
* @return DataSeriesValues[]
*/
public function getPlotValues(): array
{
return $this->plotValues;
}
/**
* Get Plot Values by Index.
*
* @return DataSeriesValues|false
*/
public function getPlotValuesByIndex(int $index): bool|DataSeriesValues
{
$keys = array_keys($this->plotValues);
if (in_array($index, $keys)) {
return $this->plotValues[$index];
}
return false;
}
/**
* Get Plot Bubble Sizes.
*
* @return DataSeriesValues[]
*/
public function getPlotBubbleSizes(): array
{
return $this->plotBubbleSizes;
}
/**
* Set Plot Bubble Sizes.
*
* @param DataSeriesValues[] $plotBubbleSizes
*/
public function setPlotBubbleSizes(array $plotBubbleSizes): self
{
$this->plotBubbleSizes = $plotBubbleSizes;
return $this;
}
/**
* Get Number of Plot Series.
*/
public function getPlotSeriesCount(): int
{
return count($this->plotValues);
}
/**
* Get Smooth Line.
*/
public function getSmoothLine(): bool
{
return $this->smoothLine;
}
/**
* Set Smooth Line.
*
* @return $this
*/
public function setSmoothLine(bool $smoothLine): static
{
$this->smoothLine = $smoothLine;
return $this;
}
public function refresh(Worksheet $worksheet): void
{
foreach ($this->plotValues as $plotValues) {
if ($plotValues !== null) {
$plotValues->refresh($worksheet, true);
}
}
foreach ($this->plotLabel as $plotValues) {
if ($plotValues !== null) {
$plotValues->refresh($worksheet, true);
}
}
foreach ($this->plotCategory as $plotValues) {
if ($plotValues !== null) {
$plotValues->refresh($worksheet, false);
}
}
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$plotLabels = $this->plotLabel;
$this->plotLabel = [];
foreach ($plotLabels as $plotLabel) {
$this->plotLabel[] = $plotLabel;
}
$plotCategories = $this->plotCategory;
$this->plotCategory = [];
foreach ($plotCategories as $plotCategory) {
$this->plotCategory[] = clone $plotCategory;
}
$plotValues = $this->plotValues;
$this->plotValues = [];
foreach ($plotValues as $plotValue) {
$this->plotValues[] = clone $plotValue;
}
$plotBubbleSizes = $this->plotBubbleSizes;
$this->plotBubbleSizes = [];
foreach ($plotBubbleSizes as $plotBubbleSize) {
$this->plotBubbleSizes[] = clone $plotBubbleSize;
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php 0000644 00000002334 15167673464 0016641 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Style\Font;
class AxisText extends Properties
{
private ?int $rotation = null;
private Font $font;
public function __construct()
{
parent::__construct();
$this->font = new Font();
$this->font->setSize(null, true);
}
public function setRotation(?int $rotation): self
{
$this->rotation = $rotation;
return $this;
}
public function getRotation(): ?int
{
return $this->rotation;
}
public function getFillColorObject(): ChartColor
{
$fillColor = $this->font->getChartColor();
if ($fillColor === null) {
$fillColor = new ChartColor();
$this->font->setChartColorFromObject($fillColor);
}
return $fillColor;
}
public function getFont(): Font
{
return $this->font;
}
public function setFont(Font $font): self
{
$this->font = $font;
return $this;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
parent::__clone();
$this->font = clone $this->font;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php 0000644 00000026265 15167673464 0016356 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Style\Font;
class Layout
{
/**
* layoutTarget.
*/
private ?string $layoutTarget = null;
/**
* X Mode.
*/
private ?string $xMode = null;
/**
* Y Mode.
*/
private ?string $yMode = null;
/**
* X-Position.
*/
private ?float $xPos = null;
/**
* Y-Position.
*/
private ?float $yPos = null;
/**
* width.
*/
private ?float $width = null;
/**
* height.
*/
private ?float $height = null;
/**
* Position - t=top.
*/
private string $dLblPos = '';
private string $numFmtCode = '';
private bool $numFmtLinked = false;
/**
* show legend key
* Specifies that legend keys should be shown in data labels.
*/
private ?bool $showLegendKey = null;
/**
* show value
* Specifies that the value should be shown in a data label.
*/
private ?bool $showVal = null;
/**
* show category name
* Specifies that the category name should be shown in the data label.
*/
private ?bool $showCatName = null;
/**
* show data series name
* Specifies that the series name should be shown in the data label.
*/
private ?bool $showSerName = null;
/**
* show percentage
* Specifies that the percentage should be shown in the data label.
*/
private ?bool $showPercent = null;
/**
* show bubble size.
*/
private ?bool $showBubbleSize = null;
/**
* show leader lines
* Specifies that leader lines should be shown for the data label.
*/
private ?bool $showLeaderLines = null;
private ?ChartColor $labelFillColor = null;
private ?ChartColor $labelBorderColor = null;
private ?Font $labelFont = null;
private ?Properties $labelEffects = null;
/**
* Create a new Layout.
*/
public function __construct(array $layout = [])
{
if (isset($layout['layoutTarget'])) {
$this->layoutTarget = $layout['layoutTarget'];
}
if (isset($layout['xMode'])) {
$this->xMode = $layout['xMode'];
}
if (isset($layout['yMode'])) {
$this->yMode = $layout['yMode'];
}
if (isset($layout['x'])) {
$this->xPos = (float) $layout['x'];
}
if (isset($layout['y'])) {
$this->yPos = (float) $layout['y'];
}
if (isset($layout['w'])) {
$this->width = (float) $layout['w'];
}
if (isset($layout['h'])) {
$this->height = (float) $layout['h'];
}
if (isset($layout['dLblPos'])) {
$this->dLblPos = (string) $layout['dLblPos'];
}
if (isset($layout['numFmtCode'])) {
$this->numFmtCode = (string) $layout['numFmtCode'];
}
$this->initBoolean($layout, 'showLegendKey');
$this->initBoolean($layout, 'showVal');
$this->initBoolean($layout, 'showCatName');
$this->initBoolean($layout, 'showSerName');
$this->initBoolean($layout, 'showPercent');
$this->initBoolean($layout, 'showBubbleSize');
$this->initBoolean($layout, 'showLeaderLines');
$this->initBoolean($layout, 'numFmtLinked');
$this->initColor($layout, 'labelFillColor');
$this->initColor($layout, 'labelBorderColor');
$labelFont = $layout['labelFont'] ?? null;
if ($labelFont instanceof Font) {
$this->labelFont = $labelFont;
}
$labelFontColor = $layout['labelFontColor'] ?? null;
if ($labelFontColor instanceof ChartColor) {
$this->setLabelFontColor($labelFontColor);
}
$labelEffects = $layout['labelEffects'] ?? null;
if ($labelEffects instanceof Properties) {
$this->labelEffects = $labelEffects;
}
}
private function initBoolean(array $layout, string $name): void
{
if (isset($layout[$name])) {
$this->$name = (bool) $layout[$name];
}
}
private function initColor(array $layout, string $name): void
{
if (isset($layout[$name]) && $layout[$name] instanceof ChartColor) {
$this->$name = $layout[$name];
}
}
/**
* Get Layout Target.
*/
public function getLayoutTarget(): ?string
{
return $this->layoutTarget;
}
/**
* Set Layout Target.
*
* @return $this
*/
public function setLayoutTarget(?string $target): static
{
$this->layoutTarget = $target;
return $this;
}
/**
* Get X-Mode.
*/
public function getXMode(): ?string
{
return $this->xMode;
}
/**
* Set X-Mode.
*
* @return $this
*/
public function setXMode(?string $mode): static
{
$this->xMode = (string) $mode;
return $this;
}
/**
* Get Y-Mode.
*/
public function getYMode(): ?string
{
return $this->yMode;
}
/**
* Set Y-Mode.
*
* @return $this
*/
public function setYMode(?string $mode): static
{
$this->yMode = (string) $mode;
return $this;
}
/**
* Get X-Position.
*/
public function getXPosition(): null|float|int
{
return $this->xPos;
}
/**
* Set X-Position.
*
* @return $this
*/
public function setXPosition(float $position): static
{
$this->xPos = $position;
return $this;
}
/**
* Get Y-Position.
*/
public function getYPosition(): ?float
{
return $this->yPos;
}
/**
* Set Y-Position.
*
* @return $this
*/
public function setYPosition(float $position): static
{
$this->yPos = $position;
return $this;
}
/**
* Get Width.
*/
public function getWidth(): ?float
{
return $this->width;
}
/**
* Set Width.
*
* @return $this
*/
public function setWidth(?float $width): static
{
$this->width = $width;
return $this;
}
/**
* Get Height.
*/
public function getHeight(): ?float
{
return $this->height;
}
/**
* Set Height.
*
* @return $this
*/
public function setHeight(?float $height): static
{
$this->height = $height;
return $this;
}
public function getShowLegendKey(): ?bool
{
return $this->showLegendKey;
}
/**
* Set show legend key
* Specifies that legend keys should be shown in data labels.
*/
public function setShowLegendKey(?bool $showLegendKey): self
{
$this->showLegendKey = $showLegendKey;
return $this;
}
public function getShowVal(): ?bool
{
return $this->showVal;
}
/**
* Set show val
* Specifies that the value should be shown in data labels.
*/
public function setShowVal(?bool $showDataLabelValues): self
{
$this->showVal = $showDataLabelValues;
return $this;
}
public function getShowCatName(): ?bool
{
return $this->showCatName;
}
/**
* Set show cat name
* Specifies that the category name should be shown in data labels.
*/
public function setShowCatName(?bool $showCategoryName): self
{
$this->showCatName = $showCategoryName;
return $this;
}
public function getShowSerName(): ?bool
{
return $this->showSerName;
}
/**
* Set show data series name.
* Specifies that the series name should be shown in data labels.
*/
public function setShowSerName(?bool $showSeriesName): self
{
$this->showSerName = $showSeriesName;
return $this;
}
public function getShowPercent(): ?bool
{
return $this->showPercent;
}
/**
* Set show percentage.
* Specifies that the percentage should be shown in data labels.
*/
public function setShowPercent(?bool $showPercentage): self
{
$this->showPercent = $showPercentage;
return $this;
}
public function getShowBubbleSize(): ?bool
{
return $this->showBubbleSize;
}
/**
* Set show bubble size.
* Specifies that the bubble size should be shown in data labels.
*/
public function setShowBubbleSize(?bool $showBubbleSize): self
{
$this->showBubbleSize = $showBubbleSize;
return $this;
}
public function getShowLeaderLines(): ?bool
{
return $this->showLeaderLines;
}
/**
* Set show leader lines.
* Specifies that leader lines should be shown in data labels.
*/
public function setShowLeaderLines(?bool $showLeaderLines): self
{
$this->showLeaderLines = $showLeaderLines;
return $this;
}
public function getLabelFillColor(): ?ChartColor
{
return $this->labelFillColor;
}
public function setLabelFillColor(?ChartColor $chartColor): self
{
$this->labelFillColor = $chartColor;
return $this;
}
public function getLabelBorderColor(): ?ChartColor
{
return $this->labelBorderColor;
}
public function setLabelBorderColor(?ChartColor $chartColor): self
{
$this->labelBorderColor = $chartColor;
return $this;
}
public function getLabelFont(): ?Font
{
return $this->labelFont;
}
public function getLabelEffects(): ?Properties
{
return $this->labelEffects;
}
public function getLabelFontColor(): ?ChartColor
{
if ($this->labelFont === null) {
return null;
}
return $this->labelFont->getChartColor();
}
public function setLabelFontColor(?ChartColor $chartColor): self
{
if ($this->labelFont === null) {
$this->labelFont = new Font();
$this->labelFont->setSize(null, true);
}
$this->labelFont->setChartColorFromObject($chartColor);
return $this;
}
public function getDLblPos(): string
{
return $this->dLblPos;
}
public function setDLblPos(string $dLblPos): self
{
$this->dLblPos = $dLblPos;
return $this;
}
public function getNumFmtCode(): string
{
return $this->numFmtCode;
}
public function setNumFmtCode(string $numFmtCode): self
{
$this->numFmtCode = $numFmtCode;
return $this;
}
public function getNumFmtLinked(): bool
{
return $this->numFmtLinked;
}
public function setNumFmtLinked(bool $numFmtLinked): self
{
$this->numFmtLinked = $numFmtLinked;
return $this;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$this->labelFillColor = ($this->labelFillColor === null) ? null : clone $this->labelFillColor;
$this->labelBorderColor = ($this->labelBorderColor === null) ? null : clone $this->labelBorderColor;
$this->labelFont = ($this->labelFont === null) ? null : clone $this->labelFont;
$this->labelEffects = ($this->labelEffects === null) ? null : clone $this->labelEffects;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt 0000644 00000001076 15167673464 0022734 0 ustar 00 ChartDirector
https://www.advsofteng.com/cdphp.html
GraPHPite
http://graphpite.sourceforge.net/
JpGraph
https://jpgraph.net/
Used composer packages:
https://packagist.org/packages/jpgraph/jpgraph (\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph)
https://packagist.org/packages/mitoteam/jpgraph (\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer)
LibChart
https://naku.dohcrew.com/libchart/pages/introduction/
pChart
http://pchart.sourceforge.net/
TeeChart
https://www.steema.com/
PHPGraphLib
http://www.ebrueggeman.com/phpgraphlib
phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php 0000644 00000002323 15167673464 0020167 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
/**
* Jpgraph is not oficially maintained in Composer, so the version there
* could be out of date. For that reason, all unit test requiring Jpgraph
* are skipped. So, do not measure code coverage for this class till that
* is fixed.
*
* This implementation uses abandoned package
* https://packagist.org/packages/jpgraph/jpgraph
*
* @codeCoverageIgnore
*/
class JpGraph extends JpGraphRendererBase
{
protected static function init(): void
{
static $loaded = false;
if ($loaded) {
return;
}
// JpGraph is no longer included with distribution, but user may install it.
// So Scrutinizer's complaint that it can't find it is reasonable, but unfixable.
\JpGraph\JpGraph::load();
\JpGraph\JpGraph::module('bar');
\JpGraph\JpGraph::module('contour');
\JpGraph\JpGraph::module('line');
\JpGraph\JpGraph::module('pie');
\JpGraph\JpGraph::module('pie3d');
\JpGraph\JpGraph::module('radar');
\JpGraph\JpGraph::module('regstat');
\JpGraph\JpGraph::module('scatter');
\JpGraph\JpGraph::module('stock');
$loaded = true;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php 0000644 00000100045 15167673464 0022451 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
use AccBarPlot;
use AccLinePlot;
use BarPlot;
use ContourPlot;
use Graph;
use GroupBarPlot;
use LinePlot;
use PhpOffice\PhpSpreadsheet\Chart\Chart;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PieGraph;
use PiePlot;
use PiePlot3D;
use PiePlotC;
use RadarGraph;
use RadarPlot;
use ScatterPlot;
use Spline;
use StockPlot;
/**
* Base class for different Jpgraph implementations as charts renderer.
*/
abstract class JpGraphRendererBase implements IRenderer
{
private const DEFAULT_WIDTH = 640.0;
private const DEFAULT_HEIGHT = 480.0;
private static $colourSet = [
'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1',
'darkmagenta', 'coral', 'dodgerblue3', 'eggplant',
'mediumblue', 'magenta', 'sandybrown', 'cyan',
'firebrick1', 'forestgreen', 'deeppink4', 'darkolivegreen',
'goldenrod2',
];
private static array $markSet;
private Chart $chart;
private $graph;
private static $plotColour = 0;
private static $plotMark = 0;
/**
* Create a new jpgraph.
*/
public function __construct(Chart $chart)
{
static::init();
$this->graph = null;
$this->chart = $chart;
self::$markSet = [
'diamond' => MARK_DIAMOND,
'square' => MARK_SQUARE,
'triangle' => MARK_UTRIANGLE,
'x' => MARK_X,
'star' => MARK_STAR,
'dot' => MARK_FILLEDCIRCLE,
'dash' => MARK_DTRIANGLE,
'circle' => MARK_CIRCLE,
'plus' => MARK_CROSS,
];
}
private function getGraphWidth(): float
{
return $this->chart->getRenderedWidth() ?? self::DEFAULT_WIDTH;
}
private function getGraphHeight(): float
{
return $this->chart->getRenderedHeight() ?? self::DEFAULT_HEIGHT;
}
/**
* This method should be overriden in descendants to do real JpGraph library initialization.
*/
abstract protected static function init(): void;
private function formatPointMarker($seriesPlot, $markerID)
{
$plotMarkKeys = array_keys(self::$markSet);
if ($markerID === null) {
// Use default plot marker (next marker in the series)
self::$plotMark %= count(self::$markSet);
$seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
} elseif ($markerID !== 'none') {
// Use specified plot marker (if it exists)
if (isset(self::$markSet[$markerID])) {
$seriesPlot->mark->SetType(self::$markSet[$markerID]);
} else {
// If the specified plot marker doesn't exist, use default plot marker (next marker in the series)
self::$plotMark %= count(self::$markSet);
$seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
}
} else {
// Hide plot marker
$seriesPlot->mark->Hide();
}
$seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]);
$seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]);
$seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
return $seriesPlot;
}
private function formatDataSetLabels(int $groupID, array $datasetLabels, $rotation = '')
{
$datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode() ?? '';
// Retrieve any label formatting code
$datasetLabelFormatCode = stripslashes($datasetLabelFormatCode);
$testCurrentIndex = 0;
foreach ($datasetLabels as $i => $datasetLabel) {
if (is_array($datasetLabel)) {
if ($rotation == 'bar') {
$datasetLabels[$i] = implode(' ', $datasetLabel);
} else {
$datasetLabel = array_reverse($datasetLabel);
$datasetLabels[$i] = implode("\n", $datasetLabel);
}
} else {
// Format labels according to any formatting code
if ($datasetLabelFormatCode !== null) {
$datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode);
}
}
++$testCurrentIndex;
}
return $datasetLabels;
}
private function percentageSumCalculation(int $groupID, $seriesCount)
{
$sumValues = [];
// Adjust our values to a percentage value across all series in the group
for ($i = 0; $i < $seriesCount; ++$i) {
if ($i == 0) {
$sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
} else {
$nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
foreach ($nextValues as $k => $value) {
if (isset($sumValues[$k])) {
$sumValues[$k] += $value;
} else {
$sumValues[$k] = $value;
}
}
}
}
return $sumValues;
}
private function percentageAdjustValues(array $dataValues, array $sumValues)
{
foreach ($dataValues as $k => $dataValue) {
$dataValues[$k] = $dataValue / $sumValues[$k] * 100;
}
return $dataValues;
}
private function getCaption($captionElement)
{
// Read any caption
$caption = ($captionElement !== null) ? $captionElement->getCaption() : null;
// Test if we have a title caption to display
if ($caption !== null) {
// If we do, it could be a plain string or an array
if (is_array($caption)) {
// Implode an array to a plain string
$caption = implode('', $caption);
}
}
return $caption;
}
private function renderTitle(): void
{
$title = $this->getCaption($this->chart->getTitle());
if ($title !== null) {
$this->graph->title->Set($title);
}
}
private function renderLegend(): void
{
$legend = $this->chart->getLegend();
if ($legend !== null) {
$legendPosition = $legend->getPosition();
switch ($legendPosition) {
case 'r':
$this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right
$this->graph->legend->SetColumns(1);
break;
case 'l':
$this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left
$this->graph->legend->SetColumns(1);
break;
case 't':
$this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top
break;
case 'b':
$this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom
break;
default:
$this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right
$this->graph->legend->SetColumns(1);
break;
}
} else {
$this->graph->legend->Hide();
}
}
private function renderCartesianPlotArea(string $type = 'textlin'): void
{
$this->graph = new Graph($this->getGraphWidth(), $this->getGraphHeight());
$this->graph->SetScale($type);
$this->renderTitle();
// Rotate for bar rather than column chart
$rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection();
$reverse = $rotation == 'bar';
$xAxisLabel = $this->chart->getXAxisLabel();
if ($xAxisLabel !== null) {
$title = $this->getCaption($xAxisLabel);
if ($title !== null) {
$this->graph->xaxis->SetTitle($title, 'center');
$this->graph->xaxis->title->SetMargin(35);
if ($reverse) {
$this->graph->xaxis->title->SetAngle(90);
$this->graph->xaxis->title->SetMargin(90);
}
}
}
$yAxisLabel = $this->chart->getYAxisLabel();
if ($yAxisLabel !== null) {
$title = $this->getCaption($yAxisLabel);
if ($title !== null) {
$this->graph->yaxis->SetTitle($title, 'center');
if ($reverse) {
$this->graph->yaxis->title->SetAngle(0);
$this->graph->yaxis->title->SetMargin(-55);
}
}
}
}
private function renderPiePlotArea(): void
{
$this->graph = new PieGraph($this->getGraphWidth(), $this->getGraphHeight());
$this->renderTitle();
}
private function renderRadarPlotArea(): void
{
$this->graph = new RadarGraph($this->getGraphWidth(), $this->getGraphHeight());
$this->graph->SetScale('lin');
$this->renderTitle();
}
private function renderPlotLine(int $groupID, bool $filled = false, bool $combination = false): void
{
$grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
$index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0];
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount();
if ($labelCount > 0) {
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels);
$this->graph->xaxis->SetTickLabels($datasetLabels);
}
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
$seriesPlots = [];
if ($grouping == 'percentStacked') {
$sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
} else {
$sumValues = [];
}
// Loop through each data series in turn
for ($i = 0; $i < $seriesCount; ++$i) {
$index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$i];
$dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues();
$marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointMarker();
if ($grouping == 'percentStacked') {
$dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
}
// Fill in any missing values in the $dataValues array
$testCurrentIndex = 0;
foreach ($dataValues as $k => $dataValue) {
while ($k != $testCurrentIndex) {
$dataValues[$testCurrentIndex] = null;
++$testCurrentIndex;
}
++$testCurrentIndex;
}
$seriesPlot = new LinePlot($dataValues);
if ($combination) {
$seriesPlot->SetBarCenter();
}
if ($filled) {
$seriesPlot->SetFilled(true);
$seriesPlot->SetColor('black');
$seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
} else {
// Set the appropriate plot marker
$this->formatPointMarker($seriesPlot, $marker);
}
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($index)->getDataValue();
$seriesPlot->SetLegend($dataLabel);
$seriesPlots[] = $seriesPlot;
}
if ($grouping == 'standard') {
$groupPlot = $seriesPlots;
} else {
$groupPlot = new AccLinePlot($seriesPlots);
}
$this->graph->Add($groupPlot);
}
private function renderPlotBar(int $groupID, ?string $dimensions = '2d'): void
{
$rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection();
// Rotate for bar rather than column chart
if (($groupID == 0) && ($rotation == 'bar')) {
$this->graph->Set90AndMargin();
}
$grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
$index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0];
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount();
if ($labelCount > 0) {
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $rotation);
// Rotate for bar rather than column chart
if ($rotation == 'bar') {
$datasetLabels = array_reverse($datasetLabels);
$this->graph->yaxis->SetPos('max');
$this->graph->yaxis->SetLabelAlign('center', 'top');
$this->graph->yaxis->SetLabelSide(SIDE_RIGHT);
}
$this->graph->xaxis->SetTickLabels($datasetLabels);
}
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
$seriesPlots = [];
if ($grouping == 'percentStacked') {
$sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
} else {
$sumValues = [];
}
// Loop through each data series in turn
for ($j = 0; $j < $seriesCount; ++$j) {
$index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$j];
$dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues();
if ($grouping == 'percentStacked') {
$dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
}
// Fill in any missing values in the $dataValues array
$testCurrentIndex = 0;
foreach ($dataValues as $k => $dataValue) {
while ($k != $testCurrentIndex) {
$dataValues[$testCurrentIndex] = null;
++$testCurrentIndex;
}
++$testCurrentIndex;
}
// Reverse the $dataValues order for bar rather than column chart
if ($rotation == 'bar') {
$dataValues = array_reverse($dataValues);
}
$seriesPlot = new BarPlot($dataValues);
$seriesPlot->SetColor('black');
$seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
if ($dimensions == '3d') {
$seriesPlot->SetShadow();
}
if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) {
$dataLabel = '';
} else {
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue();
}
$seriesPlot->SetLegend($dataLabel);
$seriesPlots[] = $seriesPlot;
}
// Reverse the plot order for bar rather than column chart
if (($rotation == 'bar') && ($grouping != 'percentStacked')) {
$seriesPlots = array_reverse($seriesPlots);
}
if ($grouping == 'clustered') {
$groupPlot = new GroupBarPlot($seriesPlots);
} elseif ($grouping == 'standard') {
$groupPlot = new GroupBarPlot($seriesPlots);
} else {
$groupPlot = new AccBarPlot($seriesPlots);
if ($dimensions == '3d') {
$groupPlot->SetShadow();
}
}
$this->graph->Add($groupPlot);
}
private function renderPlotScatter(int $groupID, bool $bubble): void
{
$scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
// Loop through each data series in turn
for ($i = 0; $i < $seriesCount; ++$i) {
$plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i);
if ($plotCategoryByIndex === false) {
$plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0);
}
$dataValuesY = $plotCategoryByIndex->getDataValues();
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
$redoDataValuesY = true;
if ($bubble) {
if (!$bubbleSize) {
$bubbleSize = '10';
}
$redoDataValuesY = false;
foreach ($dataValuesY as $dataValueY) {
if (!is_int($dataValueY) && !is_float($dataValueY)) {
$redoDataValuesY = true;
break;
}
}
}
if ($redoDataValuesY) {
foreach ($dataValuesY as $k => $dataValueY) {
$dataValuesY[$k] = $k;
}
}
$seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY);
if ($scatterStyle == 'lineMarker') {
$seriesPlot->SetLinkPoints();
$seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]);
} elseif ($scatterStyle == 'smoothMarker') {
$spline = new Spline($dataValuesY, $dataValuesX);
[$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * $this->getGraphWidth() / 20);
$lplot = new LinePlot($splineDataX, $splineDataY);
$lplot->SetColor(self::$colourSet[self::$plotColour]);
$this->graph->Add($lplot);
}
if ($bubble) {
$this->formatPointMarker($seriesPlot, 'dot');
$seriesPlot->mark->SetColor('black');
$seriesPlot->mark->SetSize($bubbleSize);
} else {
$marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
$this->formatPointMarker($seriesPlot, $marker);
}
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
$seriesPlot->SetLegend($dataLabel);
$this->graph->Add($seriesPlot);
}
}
private function renderPlotRadar(int $groupID): void
{
$radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
// Loop through each data series in turn
for ($i = 0; $i < $seriesCount; ++$i) {
$dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
$marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
$dataValues = [];
foreach ($dataValuesY as $k => $dataValueY) {
$dataValues[$k] = is_array($dataValueY) ? implode(' ', array_reverse($dataValueY)) : $dataValueY;
}
$tmp = array_shift($dataValues);
$dataValues[] = $tmp;
$tmp = array_shift($dataValuesX);
$dataValuesX[] = $tmp;
$this->graph->SetTitles(array_reverse($dataValues));
$seriesPlot = new RadarPlot(array_reverse($dataValuesX));
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
$seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
if ($radarStyle == 'filled') {
$seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]);
}
$this->formatPointMarker($seriesPlot, $marker);
$seriesPlot->SetLegend($dataLabel);
$this->graph->Add($seriesPlot);
}
}
private function renderPlotContour(int $groupID): void
{
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
$dataValues = [];
// Loop through each data series in turn
for ($i = 0; $i < $seriesCount; ++$i) {
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
$dataValues[$i] = $dataValuesX;
}
$seriesPlot = new ContourPlot($dataValues);
$this->graph->Add($seriesPlot);
}
private function renderPlotStock(int $groupID): void
{
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
$plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder();
$dataValues = [];
// Loop through each data series in turn and build the plot arrays
foreach ($plotOrder as $i => $v) {
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v);
if ($dataValuesX === false) {
continue;
}
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues();
foreach ($dataValuesX as $j => $dataValueX) {
$dataValues[$plotOrder[$i]][$j] = $dataValueX;
}
}
if (empty($dataValues)) {
return;
}
$dataValuesPlot = [];
// Flatten the plot arrays to a single dimensional array to work with jpgraph
$jMax = count($dataValues[0]);
for ($j = 0; $j < $jMax; ++$j) {
for ($i = 0; $i < $seriesCount; ++$i) {
$dataValuesPlot[] = $dataValues[$i][$j] ?? null;
}
}
// Set the x-axis labels
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
if ($labelCount > 0) {
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels);
$this->graph->xaxis->SetTickLabels($datasetLabels);
}
$seriesPlot = new StockPlot($dataValuesPlot);
$seriesPlot->SetWidth(20);
$this->graph->Add($seriesPlot);
}
private function renderAreaChart($groupCount): void
{
$this->renderCartesianPlotArea();
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotLine($i, true, false);
}
}
private function renderLineChart($groupCount): void
{
$this->renderCartesianPlotArea();
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotLine($i, false, false);
}
}
private function renderBarChart($groupCount, ?string $dimensions = '2d'): void
{
$this->renderCartesianPlotArea();
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotBar($i, $dimensions);
}
}
private function renderScatterChart($groupCount): void
{
$this->renderCartesianPlotArea('linlin');
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotScatter($i, false);
}
}
private function renderBubbleChart($groupCount): void
{
$this->renderCartesianPlotArea('linlin');
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotScatter($i, true);
}
}
private function renderPieChart($groupCount, ?string $dimensions = '2d', bool $doughnut = false, bool $multiplePlots = false): void
{
$this->renderPiePlotArea();
$iLimit = ($multiplePlots) ? $groupCount : 1;
for ($groupID = 0; $groupID < $iLimit; ++$groupID) {
$exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
$datasetLabels = [];
if ($groupID == 0) {
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
if ($labelCount > 0) {
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels);
}
}
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
// For pie charts, we only display the first series: doughnut charts generally display all series
$jLimit = ($multiplePlots) ? $seriesCount : 1;
// Loop through each data series in turn
for ($j = 0; $j < $jLimit; ++$j) {
$dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues();
// Fill in any missing values in the $dataValues array
$testCurrentIndex = 0;
foreach ($dataValues as $k => $dataValue) {
while ($k != $testCurrentIndex) {
$dataValues[$testCurrentIndex] = null;
++$testCurrentIndex;
}
++$testCurrentIndex;
}
if ($dimensions == '3d') {
$seriesPlot = new PiePlot3D($dataValues);
} else {
if ($doughnut) {
$seriesPlot = new PiePlotC($dataValues);
} else {
$seriesPlot = new PiePlot($dataValues);
}
}
if ($multiplePlots) {
$seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4));
}
if ($doughnut && method_exists($seriesPlot, 'SetMidColor')) {
$seriesPlot->SetMidColor('white');
}
$seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
if (count($datasetLabels) > 0) {
$seriesPlot->SetLabels(array_fill(0, count($datasetLabels), ''));
}
if ($dimensions != '3d') {
$seriesPlot->SetGuideLines(false);
}
if ($j == 0) {
if ($exploded) {
$seriesPlot->ExplodeAll();
}
$seriesPlot->SetLegends($datasetLabels);
}
$this->graph->Add($seriesPlot);
}
}
}
private function renderRadarChart($groupCount): void
{
$this->renderRadarPlotArea();
for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
$this->renderPlotRadar($groupID);
}
}
private function renderStockChart($groupCount): void
{
$this->renderCartesianPlotArea('intint');
for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
$this->renderPlotStock($groupID);
}
}
private function renderContourChart($groupCount): void
{
$this->renderCartesianPlotArea('intint');
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotContour($i);
}
}
private function renderCombinationChart($groupCount, $outputDestination): bool
{
$this->renderCartesianPlotArea();
for ($i = 0; $i < $groupCount; ++$i) {
$dimensions = null;
$chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
switch ($chartType) {
case 'area3DChart':
case 'areaChart':
$this->renderPlotLine($i, true, true);
break;
case 'bar3DChart':
$dimensions = '3d';
// no break
case 'barChart':
$this->renderPlotBar($i, $dimensions);
break;
case 'line3DChart':
case 'lineChart':
$this->renderPlotLine($i, false, true);
break;
case 'scatterChart':
$this->renderPlotScatter($i, false);
break;
case 'bubbleChart':
$this->renderPlotScatter($i, true);
break;
default:
$this->graph = null;
return false;
}
}
$this->renderLegend();
$this->graph->Stroke($outputDestination);
return true;
}
public function render(?string $outputDestination): bool
{
self::$plotColour = 0;
$groupCount = $this->chart->getPlotArea()->getPlotGroupCount();
$dimensions = null;
if ($groupCount == 1) {
$chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType();
} else {
$chartTypes = [];
for ($i = 0; $i < $groupCount; ++$i) {
$chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
}
$chartTypes = array_unique($chartTypes);
if (count($chartTypes) == 1) {
$chartType = array_pop($chartTypes);
} elseif (count($chartTypes) == 0) {
echo 'Chart is not yet implemented<br />';
return false;
} else {
return $this->renderCombinationChart($groupCount, $outputDestination);
}
}
switch ($chartType) {
case 'area3DChart':
$dimensions = '3d';
// no break
case 'areaChart':
$this->renderAreaChart($groupCount);
break;
case 'bar3DChart':
$dimensions = '3d';
// no break
case 'barChart':
$this->renderBarChart($groupCount, $dimensions);
break;
case 'line3DChart':
$dimensions = '3d';
// no break
case 'lineChart':
$this->renderLineChart($groupCount);
break;
case 'pie3DChart':
$dimensions = '3d';
// no break
case 'pieChart':
$this->renderPieChart($groupCount, $dimensions, false, false);
break;
case 'doughnut3DChart':
$dimensions = '3d';
// no break
case 'doughnutChart':
$this->renderPieChart($groupCount, $dimensions, true, true);
break;
case 'scatterChart':
$this->renderScatterChart($groupCount);
break;
case 'bubbleChart':
$this->renderBubbleChart($groupCount);
break;
case 'radarChart':
$this->renderRadarChart($groupCount);
break;
case 'surface3DChart':
case 'surfaceChart':
$this->renderContourChart($groupCount);
break;
case 'stockChart':
$this->renderStockChart($groupCount);
break;
default:
echo $chartType . ' is not yet implemented<br />';
return false;
}
$this->renderLegend();
$this->graph->Stroke($outputDestination);
return true;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php 0000644 00000001465 15167673464 0022165 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
use mitoteam\jpgraph\MtJpGraph;
/**
* Jpgraph is not officially maintained by Composer at packagist.org.
*
* This renderer implementation uses package
* https://packagist.org/packages/mitoteam/jpgraph
*
* This package is up to date for June 2023 and has PHP 8.2 support.
*/
class MtJpGraphRenderer extends JpGraphRendererBase
{
protected static function init(): void
{
static $loaded = false;
if ($loaded) {
return;
}
MtJpGraph::load([
'bar',
'contour',
'line',
'pie',
'pie3d',
'radar',
'regstat',
'scatter',
'stock',
], true); // enable Extended mode
$loaded = true;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php 0000644 00000000701 15167673464 0020511 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
use PhpOffice\PhpSpreadsheet\Chart\Chart;
interface IRenderer
{
/**
* IRenderer constructor.
*/
public function __construct(Chart $chart);
/**
* Render the chart to given file (or stream).
*
* @param ?string $filename Name of the file render to
*
* @return bool true on success
*/
public function render(?string $filename): bool;
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php 0000644 00000010205 15167673464 0016145 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Font;
class Title
{
public const TITLE_CELL_REFERENCE
= '/^(.*)!' // beginning of string, everything up to ! is match[1]
. '[$]([A-Z]{1,3})' // absolute column string match[2]
. '[$](\d{1,7})$/i'; // absolute row string match[3]
/**
* Title Caption.
*
* @var array<RichText|string>|RichText|string
*/
private array|RichText|string $caption;
/**
* Allow overlay of other elements?
*/
private bool $overlay = true;
/**
* Title Layout.
*/
private ?Layout $layout;
private string $cellReference = '';
private ?Font $font = null;
/**
* Create a new Title.
*/
public function __construct(array|RichText|string $caption = '', ?Layout $layout = null, bool $overlay = false)
{
$this->caption = $caption;
$this->layout = $layout;
$this->setOverlay($overlay);
}
/**
* Get caption.
*/
public function getCaption(): array|RichText|string
{
return $this->caption;
}
public function getCaptionText(?Spreadsheet $spreadsheet = null): string
{
if ($spreadsheet !== null) {
$caption = $this->getCalculatedTitle($spreadsheet);
if ($caption !== null) {
return $caption;
}
}
$caption = $this->caption;
if (is_string($caption)) {
return $caption;
}
if ($caption instanceof RichText) {
return $caption->getPlainText();
}
$retVal = '';
foreach ($caption as $textx) {
/** @var RichText|string $text */
$text = $textx;
if ($text instanceof RichText) {
$retVal .= $text->getPlainText();
} else {
$retVal .= $text;
}
}
return $retVal;
}
/**
* Set caption.
*
* @return $this
*/
public function setCaption(array|RichText|string $caption): static
{
$this->caption = $caption;
return $this;
}
/**
* Get allow overlay of other elements?
*/
public function getOverlay(): bool
{
return $this->overlay;
}
/**
* Set allow overlay of other elements?
*/
public function setOverlay(bool $overlay): self
{
$this->overlay = $overlay;
return $this;
}
public function getLayout(): ?Layout
{
return $this->layout;
}
public function setCellReference(string $cellReference): self
{
$this->cellReference = $cellReference;
return $this;
}
public function getCellReference(): string
{
return $this->cellReference;
}
public function getCalculatedTitle(?Spreadsheet $spreadsheet): ?string
{
preg_match(self::TITLE_CELL_REFERENCE, $this->cellReference, $matches);
if (count($matches) === 0 || $spreadsheet === null) {
return null;
}
$sheetName = preg_replace("/^'(.*)'$/", '$1', $matches[1]) ?? '';
return $spreadsheet->getSheetByName($sheetName)?->getCell($matches[2] . $matches[3])?->getFormattedValue();
}
public function getFont(): ?Font
{
return $this->font;
}
public function setFont(?Font $font): self
{
$this->font = $font;
return $this;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$this->layout = ($this->layout === null) ? null : clone $this->layout;
$this->font = ($this->font === null) ? null : clone $this->font;
if (is_array($this->caption)) {
$captions = $this->caption;
$this->caption = [];
foreach ($captions as $caption) {
$this->caption[] = is_object($caption) ? (clone $caption) : $caption;
}
} else {
$this->caption = is_object($this->caption) ? (clone $this->caption) : $this->caption;
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php 0000644 00000000267 15167673464 0016753 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
/**
* Created by PhpStorm.
* User: Wiktor Trzonkowski
* Date: 7/2/14
* Time: 2:36 PM.
*/
class GridLines extends Properties
{
}
phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php 0000644 00000011023 15167673464 0016747 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
class TrendLine extends Properties
{
const TRENDLINE_EXPONENTIAL = 'exp';
const TRENDLINE_LINEAR = 'linear';
const TRENDLINE_LOGARITHMIC = 'log';
const TRENDLINE_POLYNOMIAL = 'poly'; // + 'order'
const TRENDLINE_POWER = 'power';
const TRENDLINE_MOVING_AVG = 'movingAvg'; // + 'period'
const TRENDLINE_TYPES = [
self::TRENDLINE_EXPONENTIAL,
self::TRENDLINE_LINEAR,
self::TRENDLINE_LOGARITHMIC,
self::TRENDLINE_POLYNOMIAL,
self::TRENDLINE_POWER,
self::TRENDLINE_MOVING_AVG,
];
private string $trendLineType = 'linear'; // TRENDLINE_LINEAR
private int $order = 2;
private int $period = 3;
private bool $dispRSqr = false;
private bool $dispEq = false;
private string $name = '';
private float $backward = 0.0;
private float $forward = 0.0;
private float $intercept = 0.0;
/**
* Create a new TrendLine object.
*/
public function __construct(
string $trendLineType = '',
?int $order = null,
?int $period = null,
bool $dispRSqr = false,
bool $dispEq = false,
?float $backward = null,
?float $forward = null,
?float $intercept = null,
?string $name = null
) {
parent::__construct();
$this->setTrendLineProperties(
$trendLineType,
$order,
$period,
$dispRSqr,
$dispEq,
$backward,
$forward,
$intercept,
$name
);
}
public function getTrendLineType(): string
{
return $this->trendLineType;
}
public function setTrendLineType(string $trendLineType): self
{
$this->trendLineType = $trendLineType;
return $this;
}
public function getOrder(): int
{
return $this->order;
}
public function setOrder(int $order): self
{
$this->order = $order;
return $this;
}
public function getPeriod(): int
{
return $this->period;
}
public function setPeriod(int $period): self
{
$this->period = $period;
return $this;
}
public function getDispRSqr(): bool
{
return $this->dispRSqr;
}
public function setDispRSqr(bool $dispRSqr): self
{
$this->dispRSqr = $dispRSqr;
return $this;
}
public function getDispEq(): bool
{
return $this->dispEq;
}
public function setDispEq(bool $dispEq): self
{
$this->dispEq = $dispEq;
return $this;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getBackward(): float
{
return $this->backward;
}
public function setBackward(float $backward): self
{
$this->backward = $backward;
return $this;
}
public function getForward(): float
{
return $this->forward;
}
public function setForward(float $forward): self
{
$this->forward = $forward;
return $this;
}
public function getIntercept(): float
{
return $this->intercept;
}
public function setIntercept(float $intercept): self
{
$this->intercept = $intercept;
return $this;
}
public function setTrendLineProperties(
?string $trendLineType = null,
?int $order = 0,
?int $period = 0,
?bool $dispRSqr = false,
?bool $dispEq = false,
?float $backward = null,
?float $forward = null,
?float $intercept = null,
?string $name = null
): self {
if (!empty($trendLineType)) {
$this->setTrendLineType($trendLineType);
}
if ($order !== null) {
$this->setOrder($order);
}
if ($period !== null) {
$this->setPeriod($period);
}
if ($dispRSqr !== null) {
$this->setDispRSqr($dispRSqr);
}
if ($dispEq !== null) {
$this->setDispEq($dispEq);
}
if ($backward !== null) {
$this->setBackward($backward);
}
if ($forward !== null) {
$this->setForward($forward);
}
if ($intercept !== null) {
$this->setIntercept($intercept);
}
if ($name !== null) {
$this->setName($name);
}
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php 0000644 00000013046 15167673464 0021546 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class BinaryComparison
{
/**
* Epsilon Precision used for comparisons in calculations.
*/
private const DELTA = 0.1e-12;
/**
* Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.
*
* @param null|string $str1 First string value for the comparison
* @param null|string $str2 Second string value for the comparison
*/
private static function strcmpLowercaseFirst(?string $str1, ?string $str2): int
{
$inversedStr1 = StringHelper::strCaseReverse($str1 ?? '');
$inversedStr2 = StringHelper::strCaseReverse($str2 ?? '');
return strcmp($inversedStr1, $inversedStr2);
}
/**
* PHP8.1 deprecates passing null to strcmp.
*
* @param null|string $str1 First string value for the comparison
* @param null|string $str2 Second string value for the comparison
*/
private static function strcmpAllowNull(?string $str1, ?string $str2): int
{
return strcmp($str1 ?? '', $str2 ?? '');
}
public static function compare(mixed $operand1, mixed $operand2, string $operator): bool
{
// Simple validate the two operands if they are string values
if (is_string($operand1) && $operand1 > '' && $operand1[0] == Calculation::FORMULA_STRING_QUOTE) {
$operand1 = Calculation::unwrapResult($operand1);
}
if (is_string($operand2) && $operand2 > '' && $operand2[0] == Calculation::FORMULA_STRING_QUOTE) {
$operand2 = Calculation::unwrapResult($operand2);
}
// Use case insensitive comparaison if not OpenOffice mode
if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) {
if (is_string($operand1)) {
$operand1 = StringHelper::strToUpper($operand1);
}
if (is_string($operand2)) {
$operand2 = StringHelper::strToUpper($operand2);
}
}
$useLowercaseFirstComparison = is_string($operand1)
&& is_string($operand2)
&& Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE;
return self::evaluateComparison($operand1, $operand2, $operator, $useLowercaseFirstComparison);
}
private static function evaluateComparison(mixed $operand1, mixed $operand2, string $operator, bool $useLowercaseFirstComparison): bool
{
return match ($operator) {
'=' => self::equal($operand1, $operand2),
'>' => self::greaterThan($operand1, $operand2, $useLowercaseFirstComparison),
'<' => self::lessThan($operand1, $operand2, $useLowercaseFirstComparison),
'>=' => self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison),
'<=' => self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison),
'<>' => self::notEqual($operand1, $operand2),
default => throw new Exception('Unsupported binary comparison operator'),
};
}
private static function equal(mixed $operand1, mixed $operand2): bool
{
if (is_numeric($operand1) && is_numeric($operand2)) {
$result = (abs($operand1 - $operand2) < self::DELTA);
} elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
$result = $operand1 == $operand2;
} else {
$result = self::strcmpAllowNull($operand1, $operand2) == 0;
}
return $result;
}
private static function greaterThanOrEqual(mixed $operand1, mixed $operand2, bool $useLowercaseFirstComparison): bool
{
if (is_numeric($operand1) && is_numeric($operand2)) {
$result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 > $operand2));
} elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
$result = $operand1 >= $operand2;
} elseif ($useLowercaseFirstComparison) {
$result = self::strcmpLowercaseFirst($operand1, $operand2) >= 0;
} else {
$result = self::strcmpAllowNull($operand1, $operand2) >= 0;
}
return $result;
}
private static function lessThanOrEqual(mixed $operand1, mixed $operand2, bool $useLowercaseFirstComparison): bool
{
if (is_numeric($operand1) && is_numeric($operand2)) {
$result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 < $operand2));
} elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
$result = $operand1 <= $operand2;
} elseif ($useLowercaseFirstComparison) {
$result = self::strcmpLowercaseFirst($operand1, $operand2) <= 0;
} else {
$result = self::strcmpAllowNull($operand1, $operand2) <= 0;
}
return $result;
}
private static function greaterThan(mixed $operand1, mixed $operand2, bool $useLowercaseFirstComparison): bool
{
return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
}
private static function lessThan(mixed $operand1, mixed $operand2, bool $useLowercaseFirstComparison): bool
{
return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
}
private static function notEqual(mixed $operand1, mixed $operand2): bool
{
return self::equal($operand1, $operand2) !== true;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php 0000644 00000007042 15167673464 0023167 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Counts;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Minimum;
class Mean
{
/**
* GEOMEAN.
*
* Returns the geometric mean of an array or range of positive data. For example, you
* can use GEOMEAN to calculate average growth rate given compound interest with
* variable rates.
*
* Excel Function:
* GEOMEAN(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function geometric(mixed ...$args): float|int|string
{
$aArgs = Functions::flattenArray($args);
$aMean = MathTrig\Operations::product($aArgs);
if (is_numeric($aMean) && ($aMean > 0)) {
$aCount = Counts::COUNT($aArgs);
if (Minimum::min($aArgs) > 0) {
return $aMean ** (1 / $aCount);
}
}
return ExcelError::NAN();
}
/**
* HARMEAN.
*
* Returns the harmonic mean of a data set. The harmonic mean is the reciprocal of the
* arithmetic mean of reciprocals.
*
* Excel Function:
* HARMEAN(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function harmonic(mixed ...$args): string|float|int
{
// Loop through arguments
$aArgs = Functions::flattenArray($args);
if (Minimum::min($aArgs) < 0) {
return ExcelError::NAN();
}
$returnValue = 0;
$aCount = 0;
foreach ($aArgs as $arg) {
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
if ($arg <= 0) {
return ExcelError::NAN();
}
$returnValue += (1 / $arg);
++$aCount;
}
}
// Return
if ($aCount > 0) {
return 1 / ($returnValue / $aCount);
}
return ExcelError::NA();
}
/**
* TRIMMEAN.
*
* Returns the mean of the interior of a data set. TRIMMEAN calculates the mean
* taken by excluding a percentage of data points from the top and bottom tails
* of a data set.
*
* Excel Function:
* TRIMEAN(value1[,value2[, ...]], $discard)
*
* @param mixed $args Data values
*/
public static function trim(mixed ...$args): float|string
{
$aArgs = Functions::flattenArray($args);
// Calculate
$percent = array_pop($aArgs);
if ((is_numeric($percent)) && (!is_string($percent))) {
if (($percent < 0) || ($percent > 1)) {
return ExcelError::NAN();
}
$mArgs = [];
foreach ($aArgs as $arg) {
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
$mArgs[] = $arg;
}
}
$discard = floor(Counts::COUNT($mArgs) * $percent / 2);
sort($mArgs);
for ($i = 0; $i < $discard; ++$i) {
array_pop($mArgs);
array_shift($mArgs);
}
return Averages::average($mArgs);
}
return ExcelError::VALUE();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Maximum.php 0000644 00000004405 15167673464 0022167 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
class Maximum extends MaxMinBase
{
/**
* MAX.
*
* MAX returns the value of the element of the values passed that has the highest value,
* with negative numbers considered smaller than positive numbers.
*
* Excel Function:
* MAX(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function max(mixed ...$args): float|int|string
{
$returnValue = null;
// Loop through arguments
$aArgs = Functions::flattenArray($args);
foreach ($aArgs as $arg) {
if (ErrorValue::isError($arg)) {
$returnValue = $arg;
break;
}
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
if (($returnValue === null) || ($arg > $returnValue)) {
$returnValue = $arg;
}
}
}
if ($returnValue === null) {
return 0;
}
return $returnValue;
}
/**
* MAXA.
*
* Returns the greatest value in a list of arguments, including numbers, text, and logical values
*
* Excel Function:
* MAXA(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function maxA(mixed ...$args): float|int|string
{
$returnValue = null;
// Loop through arguments
$aArgs = Functions::flattenArray($args);
foreach ($aArgs as $arg) {
if (ErrorValue::isError($arg)) {
$returnValue = $arg;
break;
}
// Is it a numeric value?
if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
$arg = self::datatypeAdjustmentAllowStrings($arg);
if (($returnValue === null) || ($arg > $returnValue)) {
$returnValue = $arg;
}
}
}
if ($returnValue === null) {
return 0;
}
return $returnValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php 0000644 00000000563 15167673464 0022537 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
abstract class MaxMinBase
{
protected static function datatypeAdjustmentAllowStrings(int|float|string|bool $value): int|float
{
if (is_bool($value)) {
return (int) $value;
} elseif (is_string($value)) {
return 0;
}
return $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php 0000644 00000010555 15167673464 0022662 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Deviations
{
/**
* DEVSQ.
*
* Returns the sum of squares of deviations of data points from their sample mean.
*
* Excel Function:
* DEVSQ(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function sumSquares(mixed ...$args): string|float
{
$aArgs = Functions::flattenArrayIndexed($args);
$aMean = Averages::average($aArgs);
if (!is_numeric($aMean)) {
return ExcelError::NAN();
}
// Return value
$returnValue = 0.0;
$aCount = -1;
foreach ($aArgs as $k => $arg) {
// Is it a numeric value?
if (
(is_bool($arg))
&& ((!Functions::isCellValue($k))
|| (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE))
) {
$arg = (int) $arg;
}
if ((is_numeric($arg)) && (!is_string($arg))) {
$returnValue += ($arg - $aMean) ** 2;
++$aCount;
}
}
return $aCount === 0 ? ExcelError::VALUE() : $returnValue;
}
/**
* KURT.
*
* Returns the kurtosis of a data set. Kurtosis characterizes the relative peakedness
* or flatness of a distribution compared with the normal distribution. Positive
* kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a
* relatively flat distribution.
*
* @param array ...$args Data Series
*/
public static function kurtosis(...$args): string|int|float
{
$aArgs = Functions::flattenArrayIndexed($args);
$mean = Averages::average($aArgs);
if (!is_numeric($mean)) {
return ExcelError::DIV0();
}
$stdDev = (float) StandardDeviations::STDEV($aArgs);
if ($stdDev > 0) {
$count = $summer = 0;
foreach ($aArgs as $k => $arg) {
if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) {
} else {
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
$summer += (($arg - $mean) / $stdDev) ** 4;
++$count;
}
}
}
if ($count > 3) {
return $summer * ($count * ($count + 1)
/ (($count - 1) * ($count - 2) * ($count - 3))) - (3 * ($count - 1) ** 2
/ (($count - 2) * ($count - 3)));
}
}
return ExcelError::DIV0();
}
/**
* SKEW.
*
* Returns the skewness of a distribution. Skewness characterizes the degree of asymmetry
* of a distribution around its mean. Positive skewness indicates a distribution with an
* asymmetric tail extending toward more positive values. Negative skewness indicates a
* distribution with an asymmetric tail extending toward more negative values.
*
* @param array ...$args Data Series
*
* @return float|int|string The result, or a string containing an error
*/
public static function skew(...$args): string|int|float
{
$aArgs = Functions::flattenArrayIndexed($args);
$mean = Averages::average($aArgs);
if (!is_numeric($mean)) {
return ExcelError::DIV0();
}
$stdDev = StandardDeviations::STDEV($aArgs);
if ($stdDev === 0.0 || is_string($stdDev)) {
return ExcelError::DIV0();
}
$count = $summer = 0;
// Loop through arguments
foreach ($aArgs as $k => $arg) {
if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) {
} elseif (!is_numeric($arg)) {
return ExcelError::VALUE();
} else {
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
$summer += (($arg - $mean) / $stdDev) ** 3;
++$count;
}
}
}
if ($count > 2) {
return $summer * ($count / (($count - 1) * ($count - 2)));
}
return ExcelError::DIV0();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php 0000644 00000007241 15167673464 0023245 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Shared\IntOrFloat;
class Permutations
{
use ArrayEnabled;
/**
* PERMUT.
*
* Returns the number of permutations for a given number of objects that can be
* selected from number objects. A permutation is any set or subset of objects or
* events where internal order is significant. Permutations are different from
* combinations, for which the internal order is not significant. Use this function
* for lottery-style probability calculations.
*
* @param mixed $numObjs Integer number of different objects
* Or can be an array of values
* @param mixed $numInSet Integer number of objects in each permutation
* Or can be an array of values
*
* @return array|float|int|string Number of permutations, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function PERMUT(mixed $numObjs, mixed $numInSet)
{
if (is_array($numObjs) || is_array($numInSet)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $numObjs, $numInSet);
}
try {
$numObjs = StatisticalValidations::validateInt($numObjs);
$numInSet = StatisticalValidations::validateInt($numInSet);
} catch (Exception $e) {
return $e->getMessage();
}
if ($numObjs < $numInSet) {
return ExcelError::NAN();
}
/** @var float|int|string */
$result1 = MathTrig\Factorial::fact($numObjs);
if (is_string($result1)) {
return $result1;
}
/** @var float|int|string */
$result2 = MathTrig\Factorial::fact($numObjs - $numInSet);
if (is_string($result2)) {
return $result2;
}
$result = round($result1 / $result2);
return IntOrFloat::evaluate($result);
}
/**
* PERMUTATIONA.
*
* Returns the number of permutations for a given number of objects (with repetitions)
* that can be selected from the total objects.
*
* @param mixed $numObjs Integer number of different objects
* Or can be an array of values
* @param mixed $numInSet Integer number of objects in each permutation
* Or can be an array of values
*
* @return array|float|int|string Number of permutations, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function PERMUTATIONA(mixed $numObjs, mixed $numInSet)
{
if (is_array($numObjs) || is_array($numInSet)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $numObjs, $numInSet);
}
try {
$numObjs = StatisticalValidations::validateInt($numObjs);
$numInSet = StatisticalValidations::validateInt($numInSet);
} catch (Exception $e) {
return $e->getMessage();
}
if ($numObjs < 0 || $numInSet < 0) {
return ExcelError::NAN();
}
$result = $numObjs ** $numInSet;
return IntOrFloat::evaluate($result);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php 0000644 00000003414 15167673464 0022606 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Confidence
{
use ArrayEnabled;
/**
* CONFIDENCE.
*
* Returns the confidence interval for a population mean
*
* @param mixed $alpha As a float
* Or can be an array of values
* @param mixed $stdDev Standard Deviation as a float
* Or can be an array of values
* @param mixed $size As an integer
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function CONFIDENCE(mixed $alpha, mixed $stdDev, mixed $size)
{
if (is_array($alpha) || is_array($stdDev) || is_array($size)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $alpha, $stdDev, $size);
}
try {
$alpha = StatisticalValidations::validateFloat($alpha);
$stdDev = StatisticalValidations::validateFloat($stdDev);
$size = StatisticalValidations::validateInt($size);
} catch (Exception $e) {
return $e->getMessage();
}
if (($alpha <= 0) || ($alpha >= 1) || ($stdDev <= 0) || ($size < 1)) {
return ExcelError::NAN();
}
/** @var float $temp */
$temp = Distributions\StandardNormal::inverse(1 - $alpha / 2);
return Functions::scalar($temp * $stdDev / sqrt($size));
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages.php 0000644 00000017076 15167673464 0022317 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Averages extends AggregateBase
{
/**
* AVEDEV.
*
* Returns the average of the absolute deviations of data points from their mean.
* AVEDEV is a measure of the variability in a data set.
*
* Excel Function:
* AVEDEV(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|string (string if result is an error)
*/
public static function averageDeviations(mixed ...$args): string|float
{
$aArgs = Functions::flattenArrayIndexed($args);
// Return value
$returnValue = 0.0;
$aMean = self::average(...$args);
if ($aMean === ExcelError::DIV0()) {
return ExcelError::NAN();
} elseif ($aMean === ExcelError::VALUE()) {
return ExcelError::VALUE();
}
$aCount = 0;
foreach ($aArgs as $k => $arg) {
$arg = self::testAcceptedBoolean($arg, $k);
// Is it a numeric value?
// Strings containing numeric values are only counted if they are string literals (not cell values)
// and then only in MS Excel and in Open Office, not in Gnumeric
if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) {
return ExcelError::VALUE();
}
if (self::isAcceptedCountable($arg, $k)) {
$returnValue += abs($arg - $aMean);
++$aCount;
}
}
// Return
if ($aCount === 0) {
return ExcelError::DIV0();
}
return $returnValue / $aCount;
}
/**
* AVERAGE.
*
* Returns the average (arithmetic mean) of the arguments
*
* Excel Function:
* AVERAGE(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|int|string (string if result is an error)
*/
public static function average(mixed ...$args): string|int|float
{
$returnValue = $aCount = 0;
// Loop through arguments
foreach (Functions::flattenArrayIndexed($args) as $k => $arg) {
$arg = self::testAcceptedBoolean($arg, $k);
// Is it a numeric value?
// Strings containing numeric values are only counted if they are string literals (not cell values)
// and then only in MS Excel and in Open Office, not in Gnumeric
if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) {
return ExcelError::VALUE();
}
if (self::isAcceptedCountable($arg, $k)) {
$returnValue += $arg;
++$aCount;
}
}
// Return
if ($aCount > 0) {
return $returnValue / $aCount;
}
return ExcelError::DIV0();
}
/**
* AVERAGEA.
*
* Returns the average of its arguments, including numbers, text, and logical values
*
* Excel Function:
* AVERAGEA(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|int|string (string if result is an error)
*/
public static function averageA(mixed ...$args): string|int|float
{
$returnValue = null;
$aCount = 0;
// Loop through arguments
foreach (Functions::flattenArrayIndexed($args) as $k => $arg) {
if (is_numeric($arg)) {
// do nothing
} elseif (is_bool($arg)) {
$arg = (int) $arg;
} elseif (!Functions::isMatrixValue($k)) {
$arg = 0;
} else {
return ExcelError::VALUE();
}
$returnValue += $arg;
++$aCount;
}
if ($aCount > 0) {
return $returnValue / $aCount;
}
return ExcelError::DIV0();
}
/**
* MEDIAN.
*
* Returns the median of the given numbers. The median is the number in the middle of a set of numbers.
*
* Excel Function:
* MEDIAN(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|string The result, or a string containing an error
*/
public static function median(mixed ...$args): float|string
{
$aArgs = Functions::flattenArray($args);
$returnValue = ExcelError::NAN();
$aArgs = self::filterArguments($aArgs);
$valueCount = count($aArgs);
if ($valueCount > 0) {
sort($aArgs, SORT_NUMERIC);
$valueCount = $valueCount / 2;
if ($valueCount == floor($valueCount)) {
$returnValue = ($aArgs[$valueCount--] + $aArgs[$valueCount]) / 2;
} else {
$valueCount = floor($valueCount);
$returnValue = $aArgs[$valueCount];
}
}
return $returnValue;
}
/**
* MODE.
*
* Returns the most frequently occurring, or repetitive, value in an array or range of data
*
* Excel Function:
* MODE(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|string The result, or a string containing an error
*/
public static function mode(mixed ...$args): float|string
{
$returnValue = ExcelError::NA();
// Loop through arguments
$aArgs = Functions::flattenArray($args);
$aArgs = self::filterArguments($aArgs);
if (!empty($aArgs)) {
return self::modeCalc($aArgs);
}
return $returnValue;
}
protected static function filterArguments(array $args): array
{
return array_filter(
$args,
function ($value): bool {
// Is it a numeric value?
return is_numeric($value) && (!is_string($value));
}
);
}
/**
* Special variant of array_count_values that isn't limited to strings and integers,
* but can work with floating point numbers as values.
*/
private static function modeCalc(array $data): float|string
{
$frequencyArray = [];
$index = 0;
$maxfreq = 0;
$maxfreqkey = '';
$maxfreqdatum = '';
foreach ($data as $datum) {
$found = false;
++$index;
foreach ($frequencyArray as $key => $value) {
if ((string) $value['value'] == (string) $datum) {
++$frequencyArray[$key]['frequency'];
$freq = $frequencyArray[$key]['frequency'];
if ($freq > $maxfreq) {
$maxfreq = $freq;
$maxfreqkey = $key;
$maxfreqdatum = $datum;
} elseif ($freq == $maxfreq) {
if ($frequencyArray[$key]['index'] < $frequencyArray[$maxfreqkey]['index']) {
$maxfreqkey = $key;
$maxfreqdatum = $datum;
}
}
$found = true;
break;
}
}
if ($found === false) {
$frequencyArray[] = [
'value' => $datum,
'frequency' => 1,
'index' => $index,
];
}
}
if ($maxfreq <= 1) {
return ExcelError::NA();
}
return $maxfreqdatum;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php 0000644 00000023041 15167673464 0023012 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DAverage;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DCount;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMax;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMin;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DSum;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Conditional
{
private const CONDITION_COLUMN_NAME = 'CONDITION';
private const VALUE_COLUMN_NAME = 'VALUE';
private const CONDITIONAL_COLUMN_NAME = 'CONDITIONAL %d';
/**
* AVERAGEIF.
*
* Returns the average value from a range of cells that contain numbers within the list of arguments
*
* Excel Function:
* AVERAGEIF(range,condition[, average_range])
*
* @param mixed $range Data values
* @param null|array|string $condition the criteria that defines which cells will be checked
* @param mixed $averageRange Data values
*/
public static function AVERAGEIF(mixed $range, null|array|string $condition, mixed $averageRange = []): null|int|float|string
{
if (!is_array($range) || !is_array($averageRange) || array_key_exists(0, $range) || array_key_exists(0, $averageRange)) {
throw new CalcException('Must specify range of cells, not any kind of literal');
}
$database = self::databaseFromRangeAndValue($range, $averageRange);
$condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $condition);
}
/**
* AVERAGEIFS.
*
* Counts the number of cells that contain numbers within the list of arguments
*
* Excel Function:
* AVERAGEIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
*
* @param mixed $args Pairs of Ranges and Criteria
*/
public static function AVERAGEIFS(mixed ...$args): null|int|float|string
{
if (empty($args)) {
return 0.0;
} elseif (count($args) === 3) {
return self::AVERAGEIF($args[1], $args[2], $args[0]);
}
foreach ($args as $arg) {
if (is_array($arg) && array_key_exists(0, $arg)) {
throw new CalcException('Must specify range of cells, not any kind of literal');
}
}
$conditions = self::buildConditionSetForValueRange(...$args);
$database = self::buildDatabaseWithValueRange(...$args);
return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
}
/**
* COUNTIF.
*
* Counts the number of cells that contain numbers within the list of arguments
*
* Excel Function:
* COUNTIF(range,condition)
*
* @param mixed[] $range Data values
* @param null|array|string $condition the criteria that defines which cells will be counted
*/
public static function COUNTIF(array $range, null|array|string $condition): string|int
{
// Filter out any empty values that shouldn't be included in a COUNT
$range = array_filter(
Functions::flattenArray($range),
fn ($value): bool => $value !== null && $value !== ''
);
$range = array_merge([[self::CONDITION_COLUMN_NAME]], array_chunk($range, 1));
$condition = array_merge([[self::CONDITION_COLUMN_NAME]], [[$condition]]);
return DCount::evaluate($range, null, $condition, false);
}
/**
* COUNTIFS.
*
* Counts the number of cells that contain numbers within the list of arguments
*
* Excel Function:
* COUNTIFS(criteria_range1, criteria1, [criteria_range2, criteria2]…)
*
* @param mixed $args Pairs of Ranges and Criteria
*/
public static function COUNTIFS(mixed ...$args): int|string
{
if (empty($args)) {
return 0;
} elseif (count($args) === 2) {
return self::COUNTIF(...$args);
}
$database = self::buildDatabase(...$args);
$conditions = self::buildConditionSet(...$args);
return DCount::evaluate($database, null, $conditions, false);
}
/**
* MAXIFS.
*
* Returns the maximum value within a range of cells that contain numbers within the list of arguments
*
* Excel Function:
* MAXIFS(max_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
*
* @param mixed $args Pairs of Ranges and Criteria
*/
public static function MAXIFS(mixed ...$args): null|float|string
{
if (empty($args)) {
return 0.0;
}
$conditions = self::buildConditionSetForValueRange(...$args);
$database = self::buildDatabaseWithValueRange(...$args);
return DMax::evaluate($database, self::VALUE_COLUMN_NAME, $conditions, false);
}
/**
* MINIFS.
*
* Returns the minimum value within a range of cells that contain numbers within the list of arguments
*
* Excel Function:
* MINIFS(min_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
*
* @param mixed $args Pairs of Ranges and Criteria
*/
public static function MINIFS(mixed ...$args): null|float|string
{
if (empty($args)) {
return 0.0;
}
$conditions = self::buildConditionSetForValueRange(...$args);
$database = self::buildDatabaseWithValueRange(...$args);
return DMin::evaluate($database, self::VALUE_COLUMN_NAME, $conditions, false);
}
/**
* SUMIF.
*
* Totals the values of cells that contain numbers within the list of arguments
*
* Excel Function:
* SUMIF(range, criteria, [sum_range])
*
* @param array $range Data values
*/
public static function SUMIF(array $range, mixed $condition, array $sumRange = []): null|float|string
{
$database = self::databaseFromRangeAndValue($range, $sumRange);
$condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $condition);
}
/**
* SUMIFS.
*
* Counts the number of cells that contain numbers within the list of arguments
*
* Excel Function:
* SUMIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
*
* @param mixed $args Pairs of Ranges and Criteria
*/
public static function SUMIFS(mixed ...$args): null|float|string
{
if (empty($args)) {
return 0.0;
} elseif (count($args) === 3) {
return self::SUMIF($args[1], $args[2], $args[0]);
}
$conditions = self::buildConditionSetForValueRange(...$args);
$database = self::buildDatabaseWithValueRange(...$args);
return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
}
/** @param array $args */
private static function buildConditionSet(...$args): array
{
$conditions = self::buildConditions(1, ...$args);
return array_map(null, ...$conditions);
}
/** @param array $args */
private static function buildConditionSetForValueRange(...$args): array
{
$conditions = self::buildConditions(2, ...$args);
if (count($conditions) === 1) {
return array_map(
fn ($value): array => [$value],
$conditions[0]
);
}
return array_map(null, ...$conditions);
}
/** @param array $args */
private static function buildConditions(int $startOffset, ...$args): array
{
$conditions = [];
$pairCount = 1;
$argumentCount = count($args);
for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) {
$conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]);
++$pairCount;
}
return $conditions;
}
/** @param array $args */
private static function buildDatabase(...$args): array
{
$database = [];
return self::buildDataSet(0, $database, ...$args);
}
/** @param array $args */
private static function buildDatabaseWithValueRange(...$args): array
{
$database = [];
$database[] = array_merge(
[self::VALUE_COLUMN_NAME],
Functions::flattenArray($args[0])
);
return self::buildDataSet(1, $database, ...$args);
}
/** @param array $args */
private static function buildDataSet(int $startOffset, array $database, ...$args): array
{
$pairCount = 1;
$argumentCount = count($args);
for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) {
$database[] = array_merge(
[sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)],
Functions::flattenArray($args[$argument])
);
++$pairCount;
}
return array_map(null, ...$database);
}
private static function databaseFromRangeAndValue(array $range, array $valueRange = []): array
{
$range = Functions::flattenArray($range);
$valueRange = Functions::flattenArray($valueRange);
if (empty($valueRange)) {
$valueRange = $range;
}
$database = array_map(null, array_merge([self::CONDITION_COLUMN_NAME], $range), array_merge([self::VALUE_COLUMN_NAME], $valueRange));
return $database;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php 0000644 00000004406 15167673464 0022166 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
class Minimum extends MaxMinBase
{
/**
* MIN.
*
* MIN returns the value of the element of the values passed that has the smallest value,
* with negative numbers considered smaller than positive numbers.
*
* Excel Function:
* MIN(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function min(mixed ...$args): float|int|string
{
$returnValue = null;
// Loop through arguments
$aArgs = Functions::flattenArray($args);
foreach ($aArgs as $arg) {
if (ErrorValue::isError($arg)) {
$returnValue = $arg;
break;
}
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
if (($returnValue === null) || ($arg < $returnValue)) {
$returnValue = $arg;
}
}
}
if ($returnValue === null) {
return 0;
}
return $returnValue;
}
/**
* MINA.
*
* Returns the smallest value in a list of arguments, including numbers, text, and logical values
*
* Excel Function:
* MINA(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function minA(mixed ...$args): float|int|string
{
$returnValue = null;
// Loop through arguments
$aArgs = Functions::flattenArray($args);
foreach ($aArgs as $arg) {
if (ErrorValue::isError($arg)) {
$returnValue = $arg;
break;
}
// Is it a numeric value?
if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
$arg = self::datatypeAdjustmentAllowStrings($arg);
if (($returnValue === null) || ($arg < $returnValue)) {
$returnValue = $arg;
}
}
}
if ($returnValue === null) {
return 0;
}
return $returnValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php 0000644 00000014313 15167673464 0023026 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Percentiles
{
public const RANK_SORT_DESCENDING = 0;
public const RANK_SORT_ASCENDING = 1;
/**
* PERCENTILE.
*
* Returns the nth percentile of values in a range..
*
* Excel Function:
* PERCENTILE(value1[,value2[, ...]],entry)
*
* @param mixed $args Data values
*
* @return float|string The result, or a string containing an error
*/
public static function PERCENTILE(mixed ...$args)
{
$aArgs = Functions::flattenArray($args);
// Calculate
$entry = array_pop($aArgs);
try {
$entry = StatisticalValidations::validateFloat($entry);
} catch (Exception $e) {
return $e->getMessage();
}
if (($entry < 0) || ($entry > 1)) {
return ExcelError::NAN();
}
$mArgs = self::percentileFilterValues($aArgs);
$mValueCount = count($mArgs);
if ($mValueCount > 0) {
sort($mArgs);
$count = Counts::COUNT($mArgs);
$index = $entry * ($count - 1);
$iBase = floor($index);
if ($index == $iBase) {
return $mArgs[$index];
}
$iNext = $iBase + 1;
$iProportion = $index - $iBase;
return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion);
}
return ExcelError::NAN();
}
/**
* PERCENTRANK.
*
* Returns the rank of a value in a data set as a percentage of the data set.
* Note that the returned rank is simply rounded to the appropriate significant digits,
* rather than floored (as MS Excel), so value 3 for a value set of 1, 2, 3, 4 will return
* 0.667 rather than 0.666
*
* @param mixed $valueSet An array of (float) values, or a reference to, a list of numbers
* @param mixed $value The number whose rank you want to find
* @param mixed $significance The (integer) number of significant digits for the returned percentage value
*
* @return float|string (string if result is an error)
*/
public static function PERCENTRANK(mixed $valueSet, mixed $value, mixed $significance = 3): string|float
{
$valueSet = Functions::flattenArray($valueSet);
$value = Functions::flattenSingleValue($value);
$significance = ($significance === null) ? 3 : Functions::flattenSingleValue($significance);
try {
$value = StatisticalValidations::validateFloat($value);
$significance = StatisticalValidations::validateInt($significance);
} catch (Exception $e) {
return $e->getMessage();
}
$valueSet = self::rankFilterValues($valueSet);
$valueCount = count($valueSet);
if ($valueCount == 0) {
return ExcelError::NA();
}
sort($valueSet, SORT_NUMERIC);
$valueAdjustor = $valueCount - 1;
if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) {
return ExcelError::NA();
}
$pos = array_search($value, $valueSet);
if ($pos === false) {
$pos = 0;
$testValue = $valueSet[0];
while ($testValue < $value) {
$testValue = $valueSet[++$pos];
}
--$pos;
$pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos]));
}
return round(((float) $pos) / $valueAdjustor, $significance);
}
/**
* QUARTILE.
*
* Returns the quartile of a data set.
*
* Excel Function:
* QUARTILE(value1[,value2[, ...]],entry)
*
* @param mixed $args Data values
*
* @return float|string The result, or a string containing an error
*/
public static function QUARTILE(mixed ...$args)
{
$aArgs = Functions::flattenArray($args);
$entry = array_pop($aArgs);
try {
$entry = StatisticalValidations::validateFloat($entry);
} catch (Exception $e) {
return $e->getMessage();
}
$entry = floor($entry);
$entry /= 4;
if (($entry < 0) || ($entry > 1)) {
return ExcelError::NAN();
}
return self::PERCENTILE($aArgs, $entry);
}
/**
* RANK.
*
* Returns the rank of a number in a list of numbers.
*
* @param mixed $value The number whose rank you want to find
* @param mixed $valueSet An array of float values, or a reference to, a list of numbers
* @param mixed $order Order to sort the values in the value set
*
* @return float|string The result, or a string containing an error (0 = Descending, 1 = Ascending)
*/
public static function RANK(mixed $value, mixed $valueSet, mixed $order = self::RANK_SORT_DESCENDING)
{
$value = Functions::flattenSingleValue($value);
$valueSet = Functions::flattenArray($valueSet);
$order = ($order === null) ? self::RANK_SORT_DESCENDING : Functions::flattenSingleValue($order);
try {
$value = StatisticalValidations::validateFloat($value);
$order = StatisticalValidations::validateInt($order);
} catch (Exception $e) {
return $e->getMessage();
}
$valueSet = self::rankFilterValues($valueSet);
if ($order === self::RANK_SORT_DESCENDING) {
rsort($valueSet, SORT_NUMERIC);
} else {
sort($valueSet, SORT_NUMERIC);
}
$pos = array_search($value, $valueSet);
if ($pos === false) {
return ExcelError::NA();
}
return ++$pos;
}
protected static function percentileFilterValues(array $dataSet): array
{
return array_filter(
$dataSet,
fn ($value): bool => is_numeric($value) && !is_string($value)
);
}
protected static function rankFilterValues(array $dataSet): array
{
return array_filter(
$dataSet,
fn ($value): bool => is_numeric($value)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php 0000644 00000004230 15167673464 0024334 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
class StandardDeviations
{
/**
* STDEV.
*
* Estimates standard deviation based on a sample. The standard deviation is a measure of how
* widely values are dispersed from the average value (the mean).
*
* Excel Function:
* STDEV(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|string The result, or a string containing an error
*/
public static function STDEV(mixed ...$args)
{
$result = Variances::VAR(...$args);
if (!is_numeric($result)) {
return $result;
}
return sqrt((float) $result);
}
/**
* STDEVA.
*
* Estimates standard deviation based on a sample, including numbers, text, and logical values
*
* Excel Function:
* STDEVA(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function STDEVA(mixed ...$args): float|string
{
$result = Variances::VARA(...$args);
if (!is_numeric($result)) {
return $result;
}
return sqrt((float) $result);
}
/**
* STDEVP.
*
* Calculates standard deviation based on the entire population
*
* Excel Function:
* STDEVP(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function STDEVP(mixed ...$args): float|string
{
$result = Variances::VARP(...$args);
if (!is_numeric($result)) {
return $result;
}
return sqrt((float) $result);
}
/**
* STDEVPA.
*
* Calculates standard deviation based on the entire population, including numbers, text, and logical values
*
* Excel Function:
* STDEVPA(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function STDEVPA(mixed ...$args): float|string
{
$result = Variances::VARPA(...$args);
if (!is_numeric($result)) {
return $result;
}
return sqrt((float) $result);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Variances.php 0000644 00000012075 15167673464 0022467 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Variances extends VarianceBase
{
/**
* VAR.
*
* Estimates variance based on a sample.
*
* Excel Function:
* VAR(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|string (string if result is an error)
*/
public static function VAR(mixed ...$args): float|string
{
$returnValue = ExcelError::DIV0();
$summerA = $summerB = 0.0;
// Loop through arguments
$aArgs = Functions::flattenArray($args);
$aCount = 0;
foreach ($aArgs as $arg) {
$arg = self::datatypeAdjustmentBooleans($arg);
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
$summerA += ($arg * $arg);
$summerB += $arg;
++$aCount;
}
}
if ($aCount > 1) {
$summerA *= $aCount;
$summerB *= $summerB;
return ($summerA - $summerB) / ($aCount * ($aCount - 1));
}
return $returnValue;
}
/**
* VARA.
*
* Estimates variance based on a sample, including numbers, text, and logical values
*
* Excel Function:
* VARA(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|string (string if result is an error)
*/
public static function VARA(mixed ...$args): string|float
{
$returnValue = ExcelError::DIV0();
$summerA = $summerB = 0.0;
// Loop through arguments
$aArgs = Functions::flattenArrayIndexed($args);
$aCount = 0;
foreach ($aArgs as $k => $arg) {
if ((is_string($arg)) && (Functions::isValue($k))) {
return ExcelError::VALUE();
} elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) {
} else {
// Is it a numeric value?
if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
$arg = self::datatypeAdjustmentAllowStrings($arg);
$summerA += ($arg * $arg);
$summerB += $arg;
++$aCount;
}
}
}
if ($aCount > 1) {
$summerA *= $aCount;
$summerB *= $summerB;
return ($summerA - $summerB) / ($aCount * ($aCount - 1));
}
return $returnValue;
}
/**
* VARP.
*
* Calculates variance based on the entire population
*
* Excel Function:
* VARP(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|string (string if result is an error)
*/
public static function VARP(mixed ...$args): float|string
{
// Return value
$returnValue = ExcelError::DIV0();
$summerA = $summerB = 0.0;
// Loop through arguments
$aArgs = Functions::flattenArray($args);
$aCount = 0;
foreach ($aArgs as $arg) {
$arg = self::datatypeAdjustmentBooleans($arg);
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
$summerA += ($arg * $arg);
$summerB += $arg;
++$aCount;
}
}
if ($aCount > 0) {
$summerA *= $aCount;
$summerB *= $summerB;
return ($summerA - $summerB) / ($aCount * $aCount);
}
return $returnValue;
}
/**
* VARPA.
*
* Calculates variance based on the entire population, including numbers, text, and logical values
*
* Excel Function:
* VARPA(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|string (string if result is an error)
*/
public static function VARPA(mixed ...$args): string|float
{
$returnValue = ExcelError::DIV0();
$summerA = $summerB = 0.0;
// Loop through arguments
$aArgs = Functions::flattenArrayIndexed($args);
$aCount = 0;
foreach ($aArgs as $k => $arg) {
if ((is_string($arg)) && (Functions::isValue($k))) {
return ExcelError::VALUE();
} elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) {
} else {
// Is it a numeric value?
if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
$arg = self::datatypeAdjustmentAllowStrings($arg);
$summerA += ($arg * $arg);
$summerB += $arg;
++$aCount;
}
}
}
if ($aCount > 0) {
$summerA *= $aCount;
$summerB *= $summerB;
return ($summerA - $summerB) / ($aCount * $aCount);
}
return $returnValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Standardize.php 0000644 00000003204 15167673464 0023016 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Standardize extends StatisticalValidations
{
use ArrayEnabled;
/**
* STANDARDIZE.
*
* Returns a normalized value from a distribution characterized by mean and standard_dev.
*
* @param array|float $value Value to normalize
* Or can be an array of values
* @param array|float $mean Mean Value
* Or can be an array of values
* @param array|float $stdDev Standard Deviation
* Or can be an array of values
*
* @return array|float|string Standardized value, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function execute($value, $mean, $stdDev): array|string|float
{
if (is_array($value) || is_array($mean) || is_array($stdDev)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $stdDev);
}
try {
$value = self::validateFloat($value);
$mean = self::validateFloat($mean);
$stdDev = self::validateFloat($stdDev);
} catch (Exception $e) {
return $e->getMessage();
}
if ($stdDev <= 0) {
return ExcelError::NAN();
}
return ($value - $mean) / $stdDev;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php 0000644 00000001265 15167673464 0023076 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
abstract class VarianceBase
{
protected static function datatypeAdjustmentAllowStrings(int|float|string|bool $value): int|float
{
if (is_bool($value)) {
return (int) $value;
} elseif (is_string($value)) {
return 0;
}
return $value;
}
protected static function datatypeAdjustmentBooleans(mixed $value): mixed
{
if (is_bool($value) && (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) {
return (int) $value;
}
return $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php 0000644 00000003651 15167673464 0023235 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
abstract class AggregateBase
{
/**
* MS Excel does not count Booleans if passed as cell values, but they are counted if passed as literals.
* OpenOffice Calc always counts Booleans.
* Gnumeric never counts Booleans.
*/
protected static function testAcceptedBoolean(mixed $arg, mixed $k): mixed
{
if (!is_bool($arg)) {
return $arg;
}
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_GNUMERIC) {
return $arg;
}
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
return (int) $arg;
}
if (!Functions::isCellValue($k)) {
return (int) $arg;
}
/*if (
(is_bool($arg)) &&
((!Functions::isCellValue($k) && (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_EXCEL)) ||
(Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE))
) {
$arg = (int) $arg;
}*/
return $arg;
}
protected static function isAcceptedCountable(mixed $arg, mixed $k, bool $countNull = false): bool
{
if ($countNull && $arg === null && !Functions::isCellValue($k) && Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_GNUMERIC) {
return true;
}
if (!is_numeric($arg)) {
return false;
}
if (!is_string($arg)) {
return true;
}
if (!Functions::isCellValue($k) && Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
return true;
}
if (!Functions::isCellValue($k) && Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_GNUMERIC) {
return true;
}
return false;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Trends.php 0000644 00000034507 15167673464 0022017 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Trend\Trend;
class Trends
{
use ArrayEnabled;
private static function filterTrendValues(array &$array1, array &$array2): void
{
foreach ($array1 as $key => $value) {
if ((is_bool($value)) || (is_string($value)) || ($value === null)) {
unset($array1[$key], $array2[$key]);
}
}
}
/**
* @param mixed $array1 should be array, but scalar is made into one
* @param mixed $array2 should be array, but scalar is made into one
*/
private static function checkTrendArrays(mixed &$array1, mixed &$array2): void
{
if (!is_array($array1)) {
$array1 = [$array1];
}
if (!is_array($array2)) {
$array2 = [$array2];
}
$array1 = Functions::flattenArray($array1);
$array2 = Functions::flattenArray($array2);
self::filterTrendValues($array1, $array2);
self::filterTrendValues($array2, $array1);
// Reset the array indexes
$array1 = array_merge($array1);
$array2 = array_merge($array2);
}
protected static function validateTrendArrays(array $yValues, array $xValues): void
{
$yValueCount = count($yValues);
$xValueCount = count($xValues);
if (($yValueCount === 0) || ($yValueCount !== $xValueCount)) {
throw new Exception(ExcelError::NA());
} elseif ($yValueCount === 1) {
throw new Exception(ExcelError::DIV0());
}
}
/**
* CORREL.
*
* Returns covariance, the average of the products of deviations for each data point pair.
*
* @param mixed $yValues array of mixed Data Series Y
* @param null|mixed $xValues array of mixed Data Series X
*/
public static function CORREL(mixed $yValues, $xValues = null): float|string
{
if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) {
return ExcelError::VALUE();
}
try {
self::checkTrendArrays($yValues, $xValues);
self::validateTrendArrays($yValues, $xValues);
} catch (Exception $e) {
return $e->getMessage();
}
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
return $bestFitLinear->getCorrelation();
}
/**
* COVAR.
*
* Returns covariance, the average of the products of deviations for each data point pair.
*
* @param mixed[] $yValues array of mixed Data Series Y
* @param mixed[] $xValues array of mixed Data Series X
*/
public static function COVAR(array $yValues, array $xValues): float|string
{
try {
self::checkTrendArrays($yValues, $xValues);
self::validateTrendArrays($yValues, $xValues);
} catch (Exception $e) {
return $e->getMessage();
}
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
return $bestFitLinear->getCovariance();
}
/**
* FORECAST.
*
* Calculates, or predicts, a future value by using existing values.
* The predicted value is a y-value for a given x-value.
*
* @param mixed $xValue Float value of X for which we want to find Y
* Or can be an array of values
* @param mixed[] $yValues array of mixed Data Series Y
* @param mixed[] $xValues array of mixed Data Series X
*
* @return array|bool|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function FORECAST(mixed $xValue, array $yValues, array $xValues)
{
if (is_array($xValue)) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $xValue, $yValues, $xValues);
}
try {
$xValue = StatisticalValidations::validateFloat($xValue);
self::checkTrendArrays($yValues, $xValues);
self::validateTrendArrays($yValues, $xValues);
} catch (Exception $e) {
return $e->getMessage();
}
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
return $bestFitLinear->getValueOfYForX($xValue);
}
/**
* GROWTH.
*
* Returns values along a predicted exponential Trend
*
* @param mixed[] $yValues Data Series Y
* @param mixed[] $xValues Data Series X
* @param mixed[] $newValues Values of X for which we want to find Y
* @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
*
* @return array<int, array<int, array<int, float>>>
*/
public static function GROWTH(array $yValues, array $xValues = [], array $newValues = [], mixed $const = true): array
{
$yValues = Functions::flattenArray($yValues);
$xValues = Functions::flattenArray($xValues);
$newValues = Functions::flattenArray($newValues);
$const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
$bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const);
if (empty($newValues)) {
$newValues = $bestFitExponential->getXValues();
}
$returnArray = [];
foreach ($newValues as $xValue) {
$returnArray[0][] = [$bestFitExponential->getValueOfYForX($xValue)];
}
return $returnArray;
}
/**
* INTERCEPT.
*
* Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values.
*
* @param mixed[] $yValues Data Series Y
* @param mixed[] $xValues Data Series X
*/
public static function INTERCEPT(array $yValues, array $xValues): float|string
{
try {
self::checkTrendArrays($yValues, $xValues);
self::validateTrendArrays($yValues, $xValues);
} catch (Exception $e) {
return $e->getMessage();
}
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
return $bestFitLinear->getIntersect();
}
/**
* LINEST.
*
* Calculates the statistics for a line by using the "least squares" method to calculate a straight line
* that best fits your data, and then returns an array that describes the line.
*
* @param mixed[] $yValues Data Series Y
* @param null|mixed[] $xValues Data Series X
* @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
* @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics
*
* @return array|string The result, or a string containing an error
*/
public static function LINEST(array $yValues, ?array $xValues = null, mixed $const = true, mixed $stats = false): string|array
{
$const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
$stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats);
if ($xValues === null) {
$xValues = $yValues;
}
try {
self::checkTrendArrays($yValues, $xValues);
self::validateTrendArrays($yValues, $xValues);
} catch (Exception $e) {
return $e->getMessage();
}
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const);
if ($stats === true) {
return [
[
$bestFitLinear->getSlope(),
$bestFitLinear->getIntersect(),
],
[
$bestFitLinear->getSlopeSE(),
($const === false) ? ExcelError::NA() : $bestFitLinear->getIntersectSE(),
],
[
$bestFitLinear->getGoodnessOfFit(),
$bestFitLinear->getStdevOfResiduals(),
],
[
$bestFitLinear->getF(),
$bestFitLinear->getDFResiduals(),
],
[
$bestFitLinear->getSSRegression(),
$bestFitLinear->getSSResiduals(),
],
];
}
return [
$bestFitLinear->getSlope(),
$bestFitLinear->getIntersect(),
];
}
/**
* LOGEST.
*
* Calculates an exponential curve that best fits the X and Y data series,
* and then returns an array that describes the line.
*
* @param mixed[] $yValues Data Series Y
* @param null|mixed[] $xValues Data Series X
* @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
* @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics
*
* @return array|string The result, or a string containing an error
*/
public static function LOGEST(array $yValues, ?array $xValues = null, mixed $const = true, mixed $stats = false): string|array
{
$const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
$stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats);
if ($xValues === null) {
$xValues = $yValues;
}
try {
self::checkTrendArrays($yValues, $xValues);
self::validateTrendArrays($yValues, $xValues);
} catch (Exception $e) {
return $e->getMessage();
}
foreach ($yValues as $value) {
if ($value < 0.0) {
return ExcelError::NAN();
}
}
$bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const);
if ($stats === true) {
return [
[
$bestFitExponential->getSlope(),
$bestFitExponential->getIntersect(),
],
[
$bestFitExponential->getSlopeSE(),
($const === false) ? ExcelError::NA() : $bestFitExponential->getIntersectSE(),
],
[
$bestFitExponential->getGoodnessOfFit(),
$bestFitExponential->getStdevOfResiduals(),
],
[
$bestFitExponential->getF(),
$bestFitExponential->getDFResiduals(),
],
[
$bestFitExponential->getSSRegression(),
$bestFitExponential->getSSResiduals(),
],
];
}
return [
$bestFitExponential->getSlope(),
$bestFitExponential->getIntersect(),
];
}
/**
* RSQ.
*
* Returns the square of the Pearson product moment correlation coefficient through data points
* in known_y's and known_x's.
*
* @param mixed[] $yValues Data Series Y
* @param mixed[] $xValues Data Series X
*
* @return float|string The result, or a string containing an error
*/
public static function RSQ(array $yValues, array $xValues)
{
try {
self::checkTrendArrays($yValues, $xValues);
self::validateTrendArrays($yValues, $xValues);
} catch (Exception $e) {
return $e->getMessage();
}
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
return $bestFitLinear->getGoodnessOfFit();
}
/**
* SLOPE.
*
* Returns the slope of the linear regression line through data points in known_y's and known_x's.
*
* @param mixed[] $yValues Data Series Y
* @param mixed[] $xValues Data Series X
*
* @return float|string The result, or a string containing an error
*/
public static function SLOPE(array $yValues, array $xValues)
{
try {
self::checkTrendArrays($yValues, $xValues);
self::validateTrendArrays($yValues, $xValues);
} catch (Exception $e) {
return $e->getMessage();
}
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
return $bestFitLinear->getSlope();
}
/**
* STEYX.
*
* Returns the standard error of the predicted y-value for each x in the regression.
*
* @param mixed[] $yValues Data Series Y
* @param mixed[] $xValues Data Series X
*/
public static function STEYX(array $yValues, array $xValues): float|string
{
try {
self::checkTrendArrays($yValues, $xValues);
self::validateTrendArrays($yValues, $xValues);
} catch (Exception $e) {
return $e->getMessage();
}
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
return $bestFitLinear->getStdevOfResiduals();
}
/**
* TREND.
*
* Returns values along a linear Trend
*
* @param mixed[] $yValues Data Series Y
* @param mixed[] $xValues Data Series X
* @param mixed[] $newValues Values of X for which we want to find Y
* @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
*
* @return array<int, array<int, array<int, float>>>
*/
public static function TREND(array $yValues, array $xValues = [], array $newValues = [], mixed $const = true): array
{
$yValues = Functions::flattenArray($yValues);
$xValues = Functions::flattenArray($xValues);
$newValues = Functions::flattenArray($newValues);
$const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const);
if (empty($newValues)) {
$newValues = $bestFitLinear->getXValues();
}
$returnArray = [];
foreach ($newValues as $xValue) {
$returnArray[0][] = [$bestFitLinear->getValueOfYForX($xValue)];
}
return $returnArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php 0000644 00000004064 15167673464 0025703 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Exponential
{
use ArrayEnabled;
/**
* EXPONDIST.
*
* Returns the exponential distribution. Use EXPONDIST to model the time between events,
* such as how long an automated bank teller takes to deliver cash. For example, you can
* use EXPONDIST to determine the probability that the process takes at most 1 minute.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
* @param mixed $lambda The parameter value as a float
* Or can be an array of values
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value, mixed $lambda, mixed $cumulative): array|string|float
{
if (is_array($value) || is_array($lambda) || is_array($cumulative)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $lambda, $cumulative);
}
try {
$value = DistributionValidations::validateFloat($value);
$lambda = DistributionValidations::validateFloat($lambda);
$cumulative = DistributionValidations::validateBool($cumulative);
} catch (Exception $e) {
return $e->getMessage();
}
if (($value < 0) || ($lambda < 0)) {
return ExcelError::NAN();
}
if ($cumulative === true) {
return 1 - exp(0 - $value * $lambda);
}
return $lambda * exp(0 - $value * $lambda);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php 0000644 00000012346 15167673464 0024441 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Gamma extends GammaBase
{
use ArrayEnabled;
/**
* GAMMA.
*
* Return the gamma function value.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function gamma(mixed $value): array|string|float
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = DistributionValidations::validateFloat($value);
} catch (Exception $e) {
return $e->getMessage();
}
if ((((int) $value) == ((float) $value)) && $value <= 0.0) {
return ExcelError::NAN();
}
return self::gammaValue($value);
}
/**
* GAMMADIST.
*
* Returns the gamma distribution.
*
* @param mixed $value Float Value at which you want to evaluate the distribution
* Or can be an array of values
* @param mixed $a Parameter to the distribution as a float
* Or can be an array of values
* @param mixed $b Parameter to the distribution as a float
* Or can be an array of values
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value, mixed $a, mixed $b, mixed $cumulative)
{
if (is_array($value) || is_array($a) || is_array($b) || is_array($cumulative)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $a, $b, $cumulative);
}
try {
$value = DistributionValidations::validateFloat($value);
$a = DistributionValidations::validateFloat($a);
$b = DistributionValidations::validateFloat($b);
$cumulative = DistributionValidations::validateBool($cumulative);
} catch (Exception $e) {
return $e->getMessage();
}
if (($value < 0) || ($a <= 0) || ($b <= 0)) {
return ExcelError::NAN();
}
return self::calculateDistribution($value, $a, $b, $cumulative);
}
/**
* GAMMAINV.
*
* Returns the inverse of the Gamma distribution.
*
* @param mixed $probability Float probability at which you want to evaluate the distribution
* Or can be an array of values
* @param mixed $alpha Parameter to the distribution as a float
* Or can be an array of values
* @param mixed $beta Parameter to the distribution as a float
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function inverse(mixed $probability, mixed $alpha, mixed $beta)
{
if (is_array($probability) || is_array($alpha) || is_array($beta)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $alpha, $beta);
}
try {
$probability = DistributionValidations::validateProbability($probability);
$alpha = DistributionValidations::validateFloat($alpha);
$beta = DistributionValidations::validateFloat($beta);
} catch (Exception $e) {
return $e->getMessage();
}
if (($alpha <= 0.0) || ($beta <= 0.0)) {
return ExcelError::NAN();
}
return self::calculateInverse($probability, $alpha, $beta);
}
/**
* GAMMALN.
*
* Returns the natural logarithm of the gamma function.
*
* @param mixed $value Float Value at which you want to evaluate the distribution
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ln(mixed $value): array|string|float
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = DistributionValidations::validateFloat($value);
} catch (Exception $e) {
return $e->getMessage();
}
if ($value <= 0) {
return ExcelError::NAN();
}
return log(self::gammaValue($value));
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php 0000644 00000025567 15167673464 0025460 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ChiSquared
{
use ArrayEnabled;
private const EPS = 2.22e-16;
/**
* CHIDIST.
*
* Returns the one-tailed probability of the chi-squared distribution.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
* @param mixed $degrees Integer degrees of freedom
* Or can be an array of values
*
* @return array|float|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distributionRightTail(mixed $value, mixed $degrees): array|string|int|float
{
if (is_array($value) || is_array($degrees)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $degrees);
}
try {
$value = DistributionValidations::validateFloat($value);
$degrees = DistributionValidations::validateInt($degrees);
} catch (Exception $e) {
return $e->getMessage();
}
if ($degrees < 1) {
return ExcelError::NAN();
}
if ($value < 0) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
return 1;
}
return ExcelError::NAN();
}
return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2) / Gamma::gammaValue($degrees / 2));
}
/**
* CHIDIST.
*
* Returns the one-tailed probability of the chi-squared distribution.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
* @param mixed $degrees Integer degrees of freedom
* Or can be an array of values
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
* Or can be an array of values
*
* @return array|float|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distributionLeftTail(mixed $value, mixed $degrees, mixed $cumulative): array|string|int|float
{
if (is_array($value) || is_array($degrees) || is_array($cumulative)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $degrees, $cumulative);
}
try {
$value = DistributionValidations::validateFloat($value);
$degrees = DistributionValidations::validateInt($degrees);
$cumulative = DistributionValidations::validateBool($cumulative);
} catch (Exception $e) {
return $e->getMessage();
}
if ($degrees < 1) {
return ExcelError::NAN();
}
if ($value < 0) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
return 1;
}
return ExcelError::NAN();
}
if ($cumulative === true) {
$temp = self::distributionRightTail($value, $degrees);
return 1 - (is_numeric($temp) ? $temp : 0);
}
return ($value ** (($degrees / 2) - 1) * exp(-$value / 2))
/ ((2 ** ($degrees / 2)) * Gamma::gammaValue($degrees / 2));
}
/**
* CHIINV.
*
* Returns the inverse of the right-tailed probability of the chi-squared distribution.
*
* @param mixed $probability Float probability at which you want to evaluate the distribution
* Or can be an array of values
* @param mixed $degrees Integer degrees of freedom
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function inverseRightTail(mixed $probability, mixed $degrees)
{
if (is_array($probability) || is_array($degrees)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees);
}
try {
$probability = DistributionValidations::validateProbability($probability);
$degrees = DistributionValidations::validateInt($degrees);
} catch (Exception $e) {
return $e->getMessage();
}
if ($degrees < 1) {
return ExcelError::NAN();
}
$callback = function ($value) use ($degrees): float {
return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2)
/ Gamma::gammaValue($degrees / 2));
};
$newtonRaphson = new NewtonRaphson($callback);
return $newtonRaphson->execute($probability);
}
/**
* CHIINV.
*
* Returns the inverse of the left-tailed probability of the chi-squared distribution.
*
* @param mixed $probability Float probability at which you want to evaluate the distribution
* Or can be an array of values
* @param mixed $degrees Integer degrees of freedom
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function inverseLeftTail(mixed $probability, mixed $degrees): array|string|float
{
if (is_array($probability) || is_array($degrees)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees);
}
try {
$probability = DistributionValidations::validateProbability($probability);
$degrees = DistributionValidations::validateInt($degrees);
} catch (Exception $e) {
return $e->getMessage();
}
if ($degrees < 1) {
return ExcelError::NAN();
}
return self::inverseLeftTailCalculation($probability, $degrees);
}
/**
* CHITEST.
*
* Uses the chi-square test to calculate the probability that the differences between two supplied data sets
* (of observed and expected frequencies), are likely to be simply due to sampling error,
* or if they are likely to be real.
*
* @param mixed $actual an array of observed frequencies
* @param mixed $expected an array of expected frequencies
*/
public static function test(mixed $actual, mixed $expected): float|string
{
$rows = count($actual);
$actual = Functions::flattenArray($actual);
$expected = Functions::flattenArray($expected);
$columns = intdiv(count($actual), $rows);
$countActuals = count($actual);
$countExpected = count($expected);
if ($countActuals !== $countExpected || $countActuals === 1) {
return ExcelError::NAN();
}
$result = 0.0;
for ($i = 0; $i < $countActuals; ++$i) {
if ($expected[$i] == 0.0) {
return ExcelError::DIV0();
} elseif ($expected[$i] < 0.0) {
return ExcelError::NAN();
}
$result += (($actual[$i] - $expected[$i]) ** 2) / $expected[$i];
}
$degrees = self::degrees($rows, $columns);
$result = Functions::scalar(self::distributionRightTail($result, $degrees));
return $result;
}
protected static function degrees(int $rows, int $columns): int
{
if ($rows === 1) {
return $columns - 1;
} elseif ($columns === 1) {
return $rows - 1;
}
return ($columns - 1) * ($rows - 1);
}
private static function inverseLeftTailCalculation(float $probability, int $degrees): float
{
// bracket the root
$min = 0;
$sd = sqrt(2.0 * $degrees);
$max = 2 * $sd;
$s = -1;
while ($s * self::pchisq($max, $degrees) > $probability * $s) {
$min = $max;
$max += 2 * $sd;
}
// Find root using bisection
$chi2 = 0.5 * ($min + $max);
while (($max - $min) > self::EPS * $chi2) {
if ($s * self::pchisq($chi2, $degrees) > $probability * $s) {
$min = $chi2;
} else {
$max = $chi2;
}
$chi2 = 0.5 * ($min + $max);
}
return $chi2;
}
private static function pchisq(float $chi2, int $degrees): float
{
return self::gammp($degrees, 0.5 * $chi2);
}
private static function gammp(int $n, float $x): float
{
if ($x < 0.5 * $n + 1) {
return self::gser($n, $x);
}
return 1 - self::gcf($n, $x);
}
// Return the incomplete gamma function P(n/2,x) evaluated by
// series representation. Algorithm from numerical recipe.
// Assume that n is a positive integer and x>0, won't check arguments.
// Relative error controlled by the eps parameter
private static function gser(int $n, float $x): float
{
/** @var float $gln */
$gln = Gamma::ln($n / 2);
$a = 0.5 * $n;
$ap = $a;
$sum = 1.0 / $a;
$del = $sum;
for ($i = 1; $i < 101; ++$i) {
++$ap;
$del = $del * $x / $ap;
$sum += $del;
if ($del < $sum * self::EPS) {
break;
}
}
return $sum * exp(-$x + $a * log($x) - $gln);
}
// Return the incomplete gamma function Q(n/2,x) evaluated by
// its continued fraction representation. Algorithm from numerical recipe.
// Assume that n is a postive integer and x>0, won't check arguments.
// Relative error controlled by the eps parameter
private static function gcf(int $n, float $x): float
{
/** @var float $gln */
$gln = Gamma::ln($n / 2);
$a = 0.5 * $n;
$b = $x + 1 - $a;
$fpmin = 1.e-300;
$c = 1 / $fpmin;
$d = 1 / $b;
$h = $d;
for ($i = 1; $i < 101; ++$i) {
$an = -$i * ($i - $a);
$b += 2;
$d = $an * $d + $b;
if (abs($d) < $fpmin) {
$d = $fpmin;
}
$c = $b + $an / $c;
if (abs($c) < $fpmin) {
$c = $fpmin;
}
$d = 1 / $d;
$del = $d * $c;
$h = $h * $del;
if (abs($del - 1) < self::EPS) {
break;
}
}
return $h * exp(-$x + $a * log($x) - $gln);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StudentT.php 0000644 00000011237 15167673464 0025167 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class StudentT
{
use ArrayEnabled;
/**
* TDIST.
*
* Returns the probability of Student's T distribution.
*
* @param mixed $value Float value for the distribution
* Or can be an array of values
* @param mixed $degrees Integer value for degrees of freedom
* Or can be an array of values
* @param mixed $tails Integer value for the number of tails (1 or 2)
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value, mixed $degrees, mixed $tails)
{
if (is_array($value) || is_array($degrees) || is_array($tails)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $degrees, $tails);
}
try {
$value = DistributionValidations::validateFloat($value);
$degrees = DistributionValidations::validateInt($degrees);
$tails = DistributionValidations::validateInt($tails);
} catch (Exception $e) {
return $e->getMessage();
}
if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) {
return ExcelError::NAN();
}
return self::calculateDistribution($value, $degrees, $tails);
}
/**
* TINV.
*
* Returns the one-tailed probability of the chi-squared distribution.
*
* @param mixed $probability Float probability for the function
* Or can be an array of values
* @param mixed $degrees Integer value for degrees of freedom
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function inverse(mixed $probability, mixed $degrees)
{
if (is_array($probability) || is_array($degrees)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees);
}
try {
$probability = DistributionValidations::validateProbability($probability);
$degrees = DistributionValidations::validateInt($degrees);
} catch (Exception $e) {
return $e->getMessage();
}
if ($degrees <= 0) {
return ExcelError::NAN();
}
$callback = fn ($value) => self::distribution($value, $degrees, 2);
$newtonRaphson = new NewtonRaphson($callback);
return $newtonRaphson->execute($probability);
}
private static function calculateDistribution(float $value, int $degrees, int $tails): float
{
// tdist, which finds the probability that corresponds to a given value
// of t with k degrees of freedom. This algorithm is translated from a
// pascal function on p81 of "Statistical Computing in Pascal" by D
// Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd:
// London). The above Pascal algorithm is itself a translation of the
// fortran algoritm "AS 3" by B E Cooper of the Atlas Computer
// Laboratory as reported in (among other places) "Applied Statistics
// Algorithms", editied by P Griffiths and I D Hill (1985; Ellis
// Horwood Ltd.; W. Sussex, England).
$tterm = $degrees;
$ttheta = atan2($value, sqrt($tterm));
$tc = cos($ttheta);
$ts = sin($ttheta);
if (($degrees % 2) === 1) {
$ti = 3;
$tterm = $tc;
} else {
$ti = 2;
$tterm = 1;
}
$tsum = $tterm;
while ($ti < $degrees) {
$tterm *= $tc * $tc * ($ti - 1) / $ti;
$tsum += $tterm;
$ti += 2;
}
$tsum *= $ts;
if (($degrees % 2) == 1) {
$tsum = Functions::M_2DIVPI * ($tsum + $ttheta);
}
$tValue = 0.5 * (1 + $tsum);
if ($tails == 1) {
return 1 - abs($tValue);
}
return 1 - abs((1 - $tValue) - $tValue);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php 0000644 00000004625 15167673464 0025052 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
class Poisson
{
use ArrayEnabled;
/**
* POISSON.
*
* Returns the Poisson distribution. A common application of the Poisson distribution
* is predicting the number of events over a specific time, such as the number of
* cars arriving at a toll plaza in 1 minute.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
* @param mixed $mean Mean value as a float
* Or can be an array of values
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value, mixed $mean, mixed $cumulative): array|string|float
{
if (is_array($value) || is_array($mean) || is_array($cumulative)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $cumulative);
}
try {
$value = DistributionValidations::validateFloat($value);
$mean = DistributionValidations::validateFloat($mean);
$cumulative = DistributionValidations::validateBool($cumulative);
} catch (Exception $e) {
return $e->getMessage();
}
if (($value < 0) || ($mean < 0)) {
return ExcelError::NAN();
}
if ($cumulative) {
$summer = 0;
$floor = floor($value);
for ($i = 0; $i <= $floor; ++$i) {
/** @var float $fact */
$fact = MathTrig\Factorial::fact($i);
$summer += $mean ** $i / $fact;
}
return exp(0 - $mean) * $summer;
}
/** @var float $fact */
$fact = MathTrig\Factorial::fact($value);
return (exp(0 - $mean) * $mean ** $value) / $fact;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php 0000644 00000005113 15167673464 0023576 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class F
{
use ArrayEnabled;
/**
* F.DIST.
*
* Returns the F probability distribution.
* You can use this function to determine whether two data sets have different degrees of diversity.
* For example, you can examine the test scores of men and women entering high school, and determine
* if the variability in the females is different from that found in the males.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
* @param mixed $u The numerator degrees of freedom as an integer
* Or can be an array of values
* @param mixed $v The denominator degrees of freedom as an integer
* Or can be an array of values
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value, mixed $u, mixed $v, mixed $cumulative): array|string|float
{
if (is_array($value) || is_array($u) || is_array($v) || is_array($cumulative)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $u, $v, $cumulative);
}
try {
$value = DistributionValidations::validateFloat($value);
$u = DistributionValidations::validateInt($u);
$v = DistributionValidations::validateInt($v);
$cumulative = DistributionValidations::validateBool($cumulative);
} catch (Exception $e) {
return $e->getMessage();
}
if ($value < 0 || $u < 1 || $v < 1) {
return ExcelError::NAN();
}
if ($cumulative) {
$adjustedValue = ($u * $value) / ($u * $value + $v);
return Beta::incompleteBeta($adjustedValue, $u / 2, $v / 2);
}
return (Gamma::gammaValue(($v + $u) / 2)
/ (Gamma::gammaValue($u / 2) * Gamma::gammaValue($v / 2)))
* (($u / $v) ** ($u / 2))
* (($value ** (($u - 2) / 2)) / ((1 + ($u / $v) * $value) ** (($u + $v) / 2)));
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php 0000644 00000023436 15167673464 0025153 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Combinations;
class Binomial
{
use ArrayEnabled;
/**
* BINOMDIST.
*
* Returns the individual term binomial distribution probability. Use BINOMDIST in problems with
* a fixed number of tests or trials, when the outcomes of any trial are only success or failure,
* when trials are independent, and when the probability of success is constant throughout the
* experiment. For example, BINOMDIST can calculate the probability that two of the next three
* babies born are male.
*
* @param mixed $value Integer number of successes in trials
* Or can be an array of values
* @param mixed $trials Integer umber of trials
* Or can be an array of values
* @param mixed $probability Probability of success on each trial as a float
* Or can be an array of values
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value, mixed $trials, mixed $probability, mixed $cumulative)
{
if (is_array($value) || is_array($trials) || is_array($probability) || is_array($cumulative)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $trials, $probability, $cumulative);
}
try {
$value = DistributionValidations::validateInt($value);
$trials = DistributionValidations::validateInt($trials);
$probability = DistributionValidations::validateProbability($probability);
$cumulative = DistributionValidations::validateBool($cumulative);
} catch (Exception $e) {
return $e->getMessage();
}
if (($value < 0) || ($value > $trials)) {
return ExcelError::NAN();
}
if ($cumulative) {
return self::calculateCumulativeBinomial($value, $trials, $probability);
}
/** @var float $comb */
$comb = Combinations::withoutRepetition($trials, $value);
return $comb * $probability ** $value
* (1 - $probability) ** ($trials - $value);
}
/**
* BINOM.DIST.RANGE.
*
* Returns returns the Binomial Distribution probability for the number of successes from a specified number
* of trials falling into a specified range.
*
* @param mixed $trials Integer number of trials
* Or can be an array of values
* @param mixed $probability Probability of success on each trial as a float
* Or can be an array of values
* @param mixed $successes The integer number of successes in trials
* Or can be an array of values
* @param mixed $limit Upper limit for successes in trials as null, or an integer
* If null, then this will indicate the same as the number of Successes
* Or can be an array of values
*
* @return array|float|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function range(mixed $trials, mixed $probability, mixed $successes, mixed $limit = null): array|string|float|int
{
if (is_array($trials) || is_array($probability) || is_array($successes) || is_array($limit)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $successes, $limit);
}
$limit = $limit ?? $successes;
try {
$trials = DistributionValidations::validateInt($trials);
$probability = DistributionValidations::validateProbability($probability);
$successes = DistributionValidations::validateInt($successes);
$limit = DistributionValidations::validateInt($limit);
} catch (Exception $e) {
return $e->getMessage();
}
if (($successes < 0) || ($successes > $trials)) {
return ExcelError::NAN();
}
if (($limit < 0) || ($limit > $trials) || $limit < $successes) {
return ExcelError::NAN();
}
$summer = 0;
for ($i = $successes; $i <= $limit; ++$i) {
/** @var float $comb */
$comb = Combinations::withoutRepetition($trials, $i);
$summer += $comb * $probability ** $i
* (1 - $probability) ** ($trials - $i);
}
return $summer;
}
/**
* NEGBINOMDIST.
*
* Returns the negative binomial distribution. NEGBINOMDIST returns the probability that
* there will be number_f failures before the number_s-th success, when the constant
* probability of a success is probability_s. This function is similar to the binomial
* distribution, except that the number of successes is fixed, and the number of trials is
* variable. Like the binomial, trials are assumed to be independent.
*
* @param mixed $failures Number of Failures as an integer
* Or can be an array of values
* @param mixed $successes Threshold number of Successes as an integer
* Or can be an array of values
* @param mixed $probability Probability of success on each trial as a float
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*
* TODO Add support for the cumulative flag not present for NEGBINOMDIST, but introduced for NEGBINOM.DIST
* The cumulative default should be false to reflect the behaviour of NEGBINOMDIST
*/
public static function negative(mixed $failures, mixed $successes, mixed $probability): array|string|float
{
if (is_array($failures) || is_array($successes) || is_array($probability)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $failures, $successes, $probability);
}
try {
$failures = DistributionValidations::validateInt($failures);
$successes = DistributionValidations::validateInt($successes);
$probability = DistributionValidations::validateProbability($probability);
} catch (Exception $e) {
return $e->getMessage();
}
if (($failures < 0) || ($successes < 1)) {
return ExcelError::NAN();
}
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
if (($failures + $successes - 1) <= 0) {
return ExcelError::NAN();
}
}
/** @var float $comb */
$comb = Combinations::withoutRepetition($failures + $successes - 1, $successes - 1);
return $comb
* ($probability ** $successes) * ((1 - $probability) ** $failures);
}
/**
* BINOM.INV.
*
* Returns the smallest value for which the cumulative binomial distribution is greater
* than or equal to a criterion value
*
* @param mixed $trials number of Bernoulli trials as an integer
* Or can be an array of values
* @param mixed $probability probability of a success on each trial as a float
* Or can be an array of values
* @param mixed $alpha criterion value as a float
* Or can be an array of values
*
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function inverse(mixed $trials, mixed $probability, mixed $alpha): array|string|int
{
if (is_array($trials) || is_array($probability) || is_array($alpha)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $alpha);
}
try {
$trials = DistributionValidations::validateInt($trials);
$probability = DistributionValidations::validateProbability($probability);
$alpha = DistributionValidations::validateFloat($alpha);
} catch (Exception $e) {
return $e->getMessage();
}
if ($trials < 0) {
return ExcelError::NAN();
} elseif (($alpha < 0.0) || ($alpha > 1.0)) {
return ExcelError::NAN();
}
$successes = 0;
while ($successes <= $trials) {
$result = self::calculateCumulativeBinomial($successes, $trials, $probability);
if ($result >= $alpha) {
break;
}
++$successes;
}
return $successes;
}
private static function calculateCumulativeBinomial(int $value, int $trials, float $probability): float|int
{
$summer = 0;
for ($i = 0; $i <= $value; ++$i) {
/** @var float $comb */
$comb = Combinations::withoutRepetition($trials, $i);
$summer += $comb * $probability ** $i
* (1 - $probability) ** ($trials - $i);
}
return $summer;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php 0000644 00000004664 15167673464 0024643 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Fisher
{
use ArrayEnabled;
/**
* FISHER.
*
* Returns the Fisher transformation at x. This transformation produces a function that
* is normally distributed rather than skewed. Use this function to perform hypothesis
* testing on the correlation coefficient.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value): array|string|float
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
DistributionValidations::validateFloat($value);
} catch (Exception $e) {
return $e->getMessage();
}
if (($value <= -1) || ($value >= 1)) {
return ExcelError::NAN();
}
return 0.5 * log((1 + $value) / (1 - $value));
}
/**
* FISHERINV.
*
* Returns the inverse of the Fisher transformation. Use this transformation when
* analyzing correlations between ranges or arrays of data. If y = FISHER(x), then
* FISHERINV(y) = x.
*
* @param mixed $probability Float probability at which you want to evaluate the distribution
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function inverse(mixed $probability): array|string|float
{
if (is_array($probability)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $probability);
}
try {
DistributionValidations::validateFloat($probability);
} catch (Exception $e) {
return $e->getMessage();
}
return (exp(2 * $probability) - 1) / (exp(2 * $probability) + 1);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php 0000644 00000004307 15167673464 0025020 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Weibull
{
use ArrayEnabled;
/**
* WEIBULL.
*
* Returns the Weibull distribution. Use this distribution in reliability
* analysis, such as calculating a device's mean time to failure.
*
* @param mixed $value Float value for the distribution
* Or can be an array of values
* @param mixed $alpha Float alpha Parameter
* Or can be an array of values
* @param mixed $beta Float beta Parameter
* Or can be an array of values
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
* Or can be an array of values
*
* @return array|float|string (string if result is an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value, mixed $alpha, mixed $beta, mixed $cumulative): array|string|float
{
if (is_array($value) || is_array($alpha) || is_array($beta) || is_array($cumulative)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $alpha, $beta, $cumulative);
}
try {
$value = DistributionValidations::validateFloat($value);
$alpha = DistributionValidations::validateFloat($alpha);
$beta = DistributionValidations::validateFloat($beta);
$cumulative = DistributionValidations::validateBool($cumulative);
} catch (Exception $e) {
return $e->getMessage();
}
if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) {
return ExcelError::NAN();
}
if ($cumulative) {
return 1 - exp(0 - ($value / $beta) ** $alpha);
}
return ($alpha / $beta ** $alpha) * $value ** ($alpha - 1) * exp(0 - ($value / $beta) ** $alpha);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php 0000644 00000014174 15167673464 0026331 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations;
class StandardNormal
{
use ArrayEnabled;
/**
* NORMSDIST.
*
* Returns the standard normal cumulative distribution function. The distribution has
* a mean of 0 (zero) and a standard deviation of one. Use this function in place of a
* table of standard normal curve areas.
*
* NOTE: We don't need to check for arrays to array-enable this function, because that is already
* handled by the logic in Normal::distribution()
* All we need to do is pass the value through as scalar or as array.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function cumulative(mixed $value)
{
return Normal::distribution($value, 0, 1, true);
}
/**
* NORM.S.DIST.
*
* Returns the standard normal cumulative distribution function. The distribution has
* a mean of 0 (zero) and a standard deviation of one. Use this function in place of a
* table of standard normal curve areas.
*
* NOTE: We don't need to check for arrays to array-enable this function, because that is already
* handled by the logic in Normal::distribution()
* All we need to do is pass the value and cumulative through as scalar or as array.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value, mixed $cumulative)
{
return Normal::distribution($value, 0, 1, $cumulative);
}
/**
* NORMSINV.
*
* Returns the inverse of the standard normal cumulative distribution
*
* @param mixed $value float probability for which we want the value
* Or can be an array of values
*
* NOTE: We don't need to check for arrays to array-enable this function, because that is already
* handled by the logic in Normal::inverse()
* All we need to do is pass the value through as scalar or as array
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function inverse(mixed $value)
{
return Normal::inverse($value, 0, 1);
}
/**
* GAUSS.
*
* Calculates the probability that a member of a standard normal population will fall between
* the mean and z standard deviations from the mean.
*
* @param mixed $value Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function gauss(mixed $value): array|string|float
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if (!is_numeric($value)) {
return ExcelError::VALUE();
}
/** @var float $dist */
$dist = self::distribution($value, true);
return $dist - 0.5;
}
/**
* ZTEST.
*
* Returns the one-tailed P-value of a z-test.
*
* For a given hypothesized population mean, x, Z.TEST returns the probability that the sample mean would be
* greater than the average of observations in the data set (array) — that is, the observed sample mean.
*
* @param mixed $dataSet The dataset should be an array of float values for the observations
* @param mixed $m0 Alpha Parameter
* Or can be an array of values
* @param mixed $sigma A null or float value for the Beta (Standard Deviation) Parameter;
* if null, we use the standard deviation of the dataset
* Or can be an array of values
*
* @return array|float|string (string if result is an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function zTest(mixed $dataSet, mixed $m0, mixed $sigma = null)
{
if (is_array($m0) || is_array($sigma)) {
return self::evaluateArrayArgumentsSubsetFrom([self::class, __FUNCTION__], 1, $dataSet, $m0, $sigma);
}
$dataSet = Functions::flattenArrayIndexed($dataSet);
if (!is_numeric($m0) || ($sigma !== null && !is_numeric($sigma))) {
return ExcelError::VALUE();
}
if ($sigma === null) {
/** @var float $sigma */
$sigma = StandardDeviations::STDEV($dataSet);
}
$n = count($dataSet);
$sub1 = Averages::average($dataSet);
if (!is_numeric($sub1)) {
return $sub1;
}
$temp = self::cumulative(($sub1 - $m0) / ($sigma / sqrt($n)));
return 1 - (is_numeric($temp) ? $temp : 0);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php 0000644 00000003244 15167673464 0026221 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class NewtonRaphson
{
private const MAX_ITERATIONS = 256;
/** @var callable */
protected $callback;
public function __construct(callable $callback)
{
$this->callback = $callback;
}
public function execute(float $probability): string|int|float
{
$xLo = 100;
$xHi = 0;
$dx = 1;
$x = $xNew = 1;
$i = 0;
while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) {
// Apply Newton-Raphson step
$result = call_user_func($this->callback, $x);
$error = $result - $probability;
if ($error == 0.0) {
$dx = 0;
} elseif ($error < 0.0) {
$xLo = $x;
} else {
$xHi = $x;
}
// Avoid division by zero
if ($result != 0.0) {
$dx = $error / $result;
$xNew = $x - $dx;
}
// If the NR fails to converge (which for example may be the
// case if the initial guess is too rough) we apply a bisection
// step to determine a more narrow interval around the root.
if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
$xNew = ($xLo + $xHi) / 2;
$dx = $xNew - $x;
}
$x = $xNew;
}
if ($i == self::MAX_ITERATIONS) {
return ExcelError::NA();
}
return $x;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php 0000644 00000001173 15167673464 0030270 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StatisticalValidations;
class DistributionValidations extends StatisticalValidations
{
public static function validateProbability(mixed $probability): float
{
$probability = self::validateFloat($probability);
if ($probability < 0.0 || $probability > 1.0) {
throw new Exception(ExcelError::NAN());
}
return $probability;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php 0000644 00000022710 15167673464 0024266 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Beta
{
use ArrayEnabled;
private const MAX_ITERATIONS = 256;
private const LOG_GAMMA_X_MAX_VALUE = 2.55e305;
private const XMININ = 2.23e-308;
/**
* BETADIST.
*
* Returns the beta distribution.
*
* @param mixed $value Float value at which you want to evaluate the distribution
* Or can be an array of values
* @param mixed $alpha Parameter to the distribution as a float
* Or can be an array of values
* @param mixed $beta Parameter to the distribution as a float
* Or can be an array of values
* @param mixed $rMin as an float
* Or can be an array of values
* @param mixed $rMax as an float
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value, mixed $alpha, mixed $beta, mixed $rMin = 0.0, mixed $rMax = 1.0): array|string|float
{
if (is_array($value) || is_array($alpha) || is_array($beta) || is_array($rMin) || is_array($rMax)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $alpha, $beta, $rMin, $rMax);
}
$rMin = $rMin ?? 0.0;
$rMax = $rMax ?? 1.0;
try {
$value = DistributionValidations::validateFloat($value);
$alpha = DistributionValidations::validateFloat($alpha);
$beta = DistributionValidations::validateFloat($beta);
$rMax = DistributionValidations::validateFloat($rMax);
$rMin = DistributionValidations::validateFloat($rMin);
} catch (Exception $e) {
return $e->getMessage();
}
if ($rMin > $rMax) {
$tmp = $rMin;
$rMin = $rMax;
$rMax = $tmp;
}
if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) {
return ExcelError::NAN();
}
$value -= $rMin;
$value /= ($rMax - $rMin);
return self::incompleteBeta($value, $alpha, $beta);
}
/**
* BETAINV.
*
* Returns the inverse of the Beta distribution.
*
* @param mixed $probability Float probability at which you want to evaluate the distribution
* Or can be an array of values
* @param mixed $alpha Parameter to the distribution as a float
* Or can be an array of values
* @param mixed $beta Parameter to the distribution as a float
* Or can be an array of values
* @param mixed $rMin Minimum value as a float
* Or can be an array of values
* @param mixed $rMax Maximum value as a float
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function inverse(mixed $probability, mixed $alpha, mixed $beta, mixed $rMin = 0.0, mixed $rMax = 1.0): array|string|float
{
if (is_array($probability) || is_array($alpha) || is_array($beta) || is_array($rMin) || is_array($rMax)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $alpha, $beta, $rMin, $rMax);
}
$rMin = $rMin ?? 0.0;
$rMax = $rMax ?? 1.0;
try {
$probability = DistributionValidations::validateProbability($probability);
$alpha = DistributionValidations::validateFloat($alpha);
$beta = DistributionValidations::validateFloat($beta);
$rMax = DistributionValidations::validateFloat($rMax);
$rMin = DistributionValidations::validateFloat($rMin);
} catch (Exception $e) {
return $e->getMessage();
}
if ($rMin > $rMax) {
$tmp = $rMin;
$rMin = $rMax;
$rMax = $tmp;
}
if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0.0)) {
return ExcelError::NAN();
}
return self::calculateInverse($probability, $alpha, $beta, $rMin, $rMax);
}
private static function calculateInverse(float $probability, float $alpha, float $beta, float $rMin, float $rMax): string|float
{
$a = 0;
$b = 2;
$guess = ($a + $b) / 2;
$i = 0;
while ((($b - $a) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) {
$guess = ($a + $b) / 2;
$result = self::distribution($guess, $alpha, $beta);
if (($result === $probability) || ($result === 0.0)) {
$b = $a;
} elseif ($result > $probability) {
$b = $guess;
} else {
$a = $guess;
}
}
if ($i === self::MAX_ITERATIONS) {
return ExcelError::NA();
}
return round($rMin + $guess * ($rMax - $rMin), 12);
}
/**
* Incomplete beta function.
*
* @author Jaco van Kooten
* @author Paul Meagher
*
* The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992).
*
* @param float $x require 0<=x<=1
* @param float $p require p>0
* @param float $q require q>0
*
* @return float 0 if x<0, p<=0, q<=0 or p+q>2.55E305 and 1 if x>1 to avoid errors and over/underflow
*/
public static function incompleteBeta(float $x, float $p, float $q): float
{
if ($x <= 0.0) {
return 0.0;
} elseif ($x >= 1.0) {
return 1.0;
} elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) {
return 0.0;
}
$beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x));
if ($x < ($p + 1.0) / ($p + $q + 2.0)) {
return $beta_gam * self::betaFraction($x, $p, $q) / $p;
}
return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q);
}
// Function cache for logBeta function
private static float $logBetaCacheP = 0.0;
private static float $logBetaCacheQ = 0.0;
private static float $logBetaCacheResult = 0.0;
/**
* The natural logarithm of the beta function.
*
* @param float $p require p>0
* @param float $q require q>0
*
* @return float 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow
*
* @author Jaco van Kooten
*/
private static function logBeta(float $p, float $q): float
{
if ($p != self::$logBetaCacheP || $q != self::$logBetaCacheQ) {
self::$logBetaCacheP = $p;
self::$logBetaCacheQ = $q;
if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) {
self::$logBetaCacheResult = 0.0;
} else {
self::$logBetaCacheResult = Gamma::logGamma($p) + Gamma::logGamma($q) - Gamma::logGamma($p + $q);
}
}
return self::$logBetaCacheResult;
}
/**
* Evaluates of continued fraction part of incomplete beta function.
* Based on an idea from Numerical Recipes (W.H. Press et al, 1992).
*
* @author Jaco van Kooten
*/
private static function betaFraction(float $x, float $p, float $q): float
{
$c = 1.0;
$sum_pq = $p + $q;
$p_plus = $p + 1.0;
$p_minus = $p - 1.0;
$h = 1.0 - $sum_pq * $x / $p_plus;
if (abs($h) < self::XMININ) {
$h = self::XMININ;
}
$h = 1.0 / $h;
$frac = $h;
$m = 1;
$delta = 0.0;
while ($m <= self::MAX_ITERATIONS && abs($delta - 1.0) > Functions::PRECISION) {
$m2 = 2 * $m;
// even index for d
$d = $m * ($q - $m) * $x / (($p_minus + $m2) * ($p + $m2));
$h = 1.0 + $d * $h;
if (abs($h) < self::XMININ) {
$h = self::XMININ;
}
$h = 1.0 / $h;
$c = 1.0 + $d / $c;
if (abs($c) < self::XMININ) {
$c = self::XMININ;
}
$frac *= $h * $c;
// odd index for d
$d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2));
$h = 1.0 + $d * $h;
if (abs($h) < self::XMININ) {
$h = self::XMININ;
}
$h = 1.0 / $h;
$c = 1.0 + $d / $c;
if (abs($c) < self::XMININ) {
$c = self::XMININ;
}
$delta = $h * $c;
$frac *= $delta;
++$m;
}
return $frac;
}
/*
private static function betaValue(float $a, float $b): float
{
return (Gamma::gammaValue($a) * Gamma::gammaValue($b)) /
Gamma::gammaValue($a + $b);
}
private static function regularizedIncompleteBeta(float $value, float $a, float $b): float
{
return self::incompleteBeta($value, $a, $b) / self::betaValue($a, $b);
}
*/
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php 0000644 00000013013 15167673464 0025301 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class LogNormal
{
use ArrayEnabled;
/**
* LOGNORMDIST.
*
* Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed
* with parameters mean and standard_dev.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
* @param mixed $mean Mean value as a float
* Or can be an array of values
* @param mixed $stdDev Standard Deviation as a float
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function cumulative(mixed $value, mixed $mean, mixed $stdDev)
{
if (is_array($value) || is_array($mean) || is_array($stdDev)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $stdDev);
}
try {
$value = DistributionValidations::validateFloat($value);
$mean = DistributionValidations::validateFloat($mean);
$stdDev = DistributionValidations::validateFloat($stdDev);
} catch (Exception $e) {
return $e->getMessage();
}
if (($value <= 0) || ($stdDev <= 0)) {
return ExcelError::NAN();
}
return StandardNormal::cumulative((log($value) - $mean) / $stdDev);
}
/**
* LOGNORM.DIST.
*
* Returns the lognormal distribution of x, where ln(x) is normally distributed
* with parameters mean and standard_dev.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
* @param mixed $mean Mean value as a float
* Or can be an array of values
* @param mixed $stdDev Standard Deviation as a float
* Or can be an array of values
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value, mixed $mean, mixed $stdDev, mixed $cumulative = false)
{
if (is_array($value) || is_array($mean) || is_array($stdDev) || is_array($cumulative)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $stdDev, $cumulative);
}
try {
$value = DistributionValidations::validateFloat($value);
$mean = DistributionValidations::validateFloat($mean);
$stdDev = DistributionValidations::validateFloat($stdDev);
$cumulative = DistributionValidations::validateBool($cumulative);
} catch (Exception $e) {
return $e->getMessage();
}
if (($value <= 0) || ($stdDev <= 0)) {
return ExcelError::NAN();
}
if ($cumulative === true) {
return StandardNormal::distribution((log($value) - $mean) / $stdDev, true);
}
return (1 / (sqrt(2 * M_PI) * $stdDev * $value))
* exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2)));
}
/**
* LOGINV.
*
* Returns the inverse of the lognormal cumulative distribution
*
* @param mixed $probability Float probability for which we want the value
* Or can be an array of values
* @param mixed $mean Mean Value as a float
* Or can be an array of values
* @param mixed $stdDev Standard Deviation as a float
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*
* @TODO Try implementing P J Acklam's refinement algorithm for greater
* accuracy if I can get my head round the mathematics
* (as described at) http://home.online.no/~pjacklam/notes/invnorm/
*/
public static function inverse(mixed $probability, mixed $mean, mixed $stdDev): array|string|float
{
if (is_array($probability) || is_array($mean) || is_array($stdDev)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $mean, $stdDev);
}
try {
$probability = DistributionValidations::validateProbability($probability);
$mean = DistributionValidations::validateFloat($mean);
$stdDev = DistributionValidations::validateFloat($stdDev);
} catch (Exception $e) {
return $e->getMessage();
}
if ($stdDev <= 0) {
return ExcelError::NAN();
}
/** @var float $inverse */
$inverse = StandardNormal::inverse($probability);
return exp($mean + $stdDev * $inverse);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php 0000644 00000016116 15167673464 0024646 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Normal
{
use ArrayEnabled;
public const SQRT2PI = 2.5066282746310005024157652848110452530069867406099;
/**
* NORMDIST.
*
* Returns the normal distribution for the specified mean and standard deviation. This
* function has a very wide range of applications in statistics, including hypothesis
* testing.
*
* @param mixed $value Float value for which we want the probability
* Or can be an array of values
* @param mixed $mean Mean value as a float
* Or can be an array of values
* @param mixed $stdDev Standard Deviation as a float
* Or can be an array of values
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $value, mixed $mean, mixed $stdDev, mixed $cumulative): array|string|float
{
if (is_array($value) || is_array($mean) || is_array($stdDev) || is_array($cumulative)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $stdDev, $cumulative);
}
try {
$value = DistributionValidations::validateFloat($value);
$mean = DistributionValidations::validateFloat($mean);
$stdDev = DistributionValidations::validateFloat($stdDev);
$cumulative = DistributionValidations::validateBool($cumulative);
} catch (Exception $e) {
return $e->getMessage();
}
if ($stdDev < 0) {
return ExcelError::NAN();
}
if ($cumulative) {
return 0.5 * (1 + Engineering\Erf::erfValue(($value - $mean) / ($stdDev * sqrt(2))));
}
return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev))));
}
/**
* NORMINV.
*
* Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation.
*
* @param mixed $probability Float probability for which we want the value
* Or can be an array of values
* @param mixed $mean Mean Value as a float
* Or can be an array of values
* @param mixed $stdDev Standard Deviation as a float
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function inverse(mixed $probability, mixed $mean, mixed $stdDev): array|string|float
{
if (is_array($probability) || is_array($mean) || is_array($stdDev)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $mean, $stdDev);
}
try {
$probability = DistributionValidations::validateProbability($probability);
$mean = DistributionValidations::validateFloat($mean);
$stdDev = DistributionValidations::validateFloat($stdDev);
} catch (Exception $e) {
return $e->getMessage();
}
if ($stdDev < 0) {
return ExcelError::NAN();
}
return (self::inverseNcdf($probability) * $stdDev) + $mean;
}
/*
* inverse_ncdf.php
* -------------------
* begin : Friday, January 16, 2004
* copyright : (C) 2004 Michael Nickerson
* email : nickersonm@yahoo.com
*
*/
private static function inverseNcdf(float $p): float
{
// Inverse ncdf approximation by Peter J. Acklam, implementation adapted to
// PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as
// a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html
// I have not checked the accuracy of this implementation. Be aware that PHP
// will truncate the coeficcients to 14 digits.
// You have permission to use and distribute this function freely for
// whatever purpose you want, but please show common courtesy and give credit
// where credit is due.
// Input paramater is $p - probability - where 0 < p < 1.
// Coefficients in rational approximations
static $a = [
1 => -3.969683028665376e+01,
2 => 2.209460984245205e+02,
3 => -2.759285104469687e+02,
4 => 1.383577518672690e+02,
5 => -3.066479806614716e+01,
6 => 2.506628277459239e+00,
];
static $b = [
1 => -5.447609879822406e+01,
2 => 1.615858368580409e+02,
3 => -1.556989798598866e+02,
4 => 6.680131188771972e+01,
5 => -1.328068155288572e+01,
];
static $c = [
1 => -7.784894002430293e-03,
2 => -3.223964580411365e-01,
3 => -2.400758277161838e+00,
4 => -2.549732539343734e+00,
5 => 4.374664141464968e+00,
6 => 2.938163982698783e+00,
];
static $d = [
1 => 7.784695709041462e-03,
2 => 3.224671290700398e-01,
3 => 2.445134137142996e+00,
4 => 3.754408661907416e+00,
];
// Define lower and upper region break-points.
$p_low = 0.02425; //Use lower region approx. below this
$p_high = 1 - $p_low; //Use upper region approx. above this
if (0 < $p && $p < $p_low) {
// Rational approximation for lower region.
$q = sqrt(-2 * log($p));
return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6])
/ (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
} elseif ($p_high < $p && $p < 1) {
// Rational approximation for upper region.
$q = sqrt(-2 * log(1 - $p));
return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6])
/ (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
}
// Rational approximation for central region.
$q = $p - 0.5;
$r = $q * $q;
return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q
/ ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php 0000644 00000006362 15167673464 0026346 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Combinations;
class HyperGeometric
{
use ArrayEnabled;
/**
* HYPGEOMDIST.
*
* Returns the hypergeometric distribution. HYPGEOMDIST returns the probability of a given number of
* sample successes, given the sample size, population successes, and population size.
*
* @param mixed $sampleSuccesses Integer number of successes in the sample
* Or can be an array of values
* @param mixed $sampleNumber Integer size of the sample
* Or can be an array of values
* @param mixed $populationSuccesses Integer number of successes in the population
* Or can be an array of values
* @param mixed $populationNumber Integer population size
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function distribution(mixed $sampleSuccesses, mixed $sampleNumber, mixed $populationSuccesses, mixed $populationNumber): array|string|float
{
if (
is_array($sampleSuccesses) || is_array($sampleNumber)
|| is_array($populationSuccesses) || is_array($populationNumber)
) {
return self::evaluateArrayArguments(
[self::class, __FUNCTION__],
$sampleSuccesses,
$sampleNumber,
$populationSuccesses,
$populationNumber
);
}
try {
$sampleSuccesses = DistributionValidations::validateInt($sampleSuccesses);
$sampleNumber = DistributionValidations::validateInt($sampleNumber);
$populationSuccesses = DistributionValidations::validateInt($populationSuccesses);
$populationNumber = DistributionValidations::validateInt($populationNumber);
} catch (Exception $e) {
return $e->getMessage();
}
if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) {
return ExcelError::NAN();
}
if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) {
return ExcelError::NAN();
}
if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) {
return ExcelError::NAN();
}
$successesPopulationAndSample = (float) Combinations::withoutRepetition($populationSuccesses, $sampleSuccesses);
$numbersPopulationAndSample = (float) Combinations::withoutRepetition($populationNumber, $sampleNumber);
$adjustedPopulationAndSample = (float) Combinations::withoutRepetition(
$populationNumber - $populationSuccesses,
$sampleNumber - $sampleSuccesses
);
return $successesPopulationAndSample * $adjustedPopulationAndSample / $numbersPopulationAndSample;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php 0000644 00000026657 15167673464 0025246 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
abstract class GammaBase
{
private const LOG_GAMMA_X_MAX_VALUE = 2.55e305;
private const EPS = 2.22e-16;
private const MAX_VALUE = 1.2e308;
private const SQRT2PI = 2.5066282746310005024157652848110452530069867406099;
private const MAX_ITERATIONS = 256;
protected static function calculateDistribution(float $value, float $a, float $b, bool $cumulative): float
{
if ($cumulative) {
return self::incompleteGamma($a, $value / $b) / self::gammaValue($a);
}
return (1 / ($b ** $a * self::gammaValue($a))) * $value ** ($a - 1) * exp(0 - ($value / $b));
}
/** @return float|string */
protected static function calculateInverse(float $probability, float $alpha, float $beta)
{
$xLo = 0;
$xHi = $alpha * $beta * 5;
$dx = 1024;
$x = $xNew = 1;
$i = 0;
while ((abs($dx) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) {
// Apply Newton-Raphson step
$result = self::calculateDistribution($x, $alpha, $beta, true);
if (!is_float($result)) {
return ExcelError::NA();
}
$error = $result - $probability;
if ($error == 0.0) {
$dx = 0;
} elseif ($error < 0.0) {
$xLo = $x;
} else {
$xHi = $x;
}
$pdf = self::calculateDistribution($x, $alpha, $beta, false);
// Avoid division by zero
if (!is_float($pdf)) {
return ExcelError::NA();
}
if ($pdf !== 0.0) {
$dx = $error / $pdf;
$xNew = $x - $dx;
}
// If the NR fails to converge (which for example may be the
// case if the initial guess is too rough) we apply a bisection
// step to determine a more narrow interval around the root.
if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) {
$xNew = ($xLo + $xHi) / 2;
$dx = $xNew - $x;
}
$x = $xNew;
}
if ($i === self::MAX_ITERATIONS) {
return ExcelError::NA();
}
return $x;
}
//
// Implementation of the incomplete Gamma function
//
public static function incompleteGamma(float $a, float $x): float
{
static $max = 32;
$summer = 0;
for ($n = 0; $n <= $max; ++$n) {
$divisor = $a;
for ($i = 1; $i <= $n; ++$i) {
$divisor *= ($a + $i);
}
$summer += ($x ** $n / $divisor);
}
return $x ** $a * exp(0 - $x) * $summer;
}
//
// Implementation of the Gamma function
//
public static function gammaValue(float $value): float
{
if ($value == 0.0) {
return 0;
}
static $p0 = 1.000000000190015;
static $p = [
1 => 76.18009172947146,
2 => -86.50532032941677,
3 => 24.01409824083091,
4 => -1.231739572450155,
5 => 1.208650973866179e-3,
6 => -5.395239384953e-6,
];
$y = $x = $value;
$tmp = $x + 5.5;
$tmp -= ($x + 0.5) * log($tmp);
$summer = $p0;
for ($j = 1; $j <= 6; ++$j) {
$summer += ($p[$j] / ++$y);
}
return exp(0 - $tmp + log(self::SQRT2PI * $summer / $x));
}
private const LG_D1 = -0.5772156649015328605195174;
private const LG_D2 = 0.4227843350984671393993777;
private const LG_D4 = 1.791759469228055000094023;
private const LG_P1 = [
4.945235359296727046734888,
201.8112620856775083915565,
2290.838373831346393026739,
11319.67205903380828685045,
28557.24635671635335736389,
38484.96228443793359990269,
26377.48787624195437963534,
7225.813979700288197698961,
];
private const LG_P2 = [
4.974607845568932035012064,
542.4138599891070494101986,
15506.93864978364947665077,
184793.2904445632425417223,
1088204.76946882876749847,
3338152.967987029735917223,
5106661.678927352456275255,
3074109.054850539556250927,
];
private const LG_P4 = [
14745.02166059939948905062,
2426813.369486704502836312,
121475557.4045093227939592,
2663432449.630976949898078,
29403789566.34553899906876,
170266573776.5398868392998,
492612579337.743088758812,
560625185622.3951465078242,
];
private const LG_Q1 = [
67.48212550303777196073036,
1113.332393857199323513008,
7738.757056935398733233834,
27639.87074403340708898585,
54993.10206226157329794414,
61611.22180066002127833352,
36351.27591501940507276287,
8785.536302431013170870835,
];
private const LG_Q2 = [
183.0328399370592604055942,
7765.049321445005871323047,
133190.3827966074194402448,
1136705.821321969608938755,
5267964.117437946917577538,
13467014.54311101692290052,
17827365.30353274213975932,
9533095.591844353613395747,
];
private const LG_Q4 = [
2690.530175870899333379843,
639388.5654300092398984238,
41355999.30241388052042842,
1120872109.61614794137657,
14886137286.78813811542398,
101680358627.2438228077304,
341747634550.7377132798597,
446315818741.9713286462081,
];
private const LG_C = [
-0.001910444077728,
8.4171387781295e-4,
-5.952379913043012e-4,
7.93650793500350248e-4,
-0.002777777777777681622553,
0.08333333333333333331554247,
0.0057083835261,
];
// Rough estimate of the fourth root of logGamma_xBig
private const LG_FRTBIG = 2.25e76;
private const PNT68 = 0.6796875;
// Function cache for logGamma
private static float $logGammaCacheResult = 0.0;
private static float $logGammaCacheX = 0.0;
/**
* logGamma function.
*
* Original author was Jaco van Kooten. Ported to PHP by Paul Meagher.
*
* The natural logarithm of the gamma function. <br />
* Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz <br />
* Applied Mathematics Division <br />
* Argonne National Laboratory <br />
* Argonne, IL 60439 <br />
* <p>
* References:
* <ol>
* <li>W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural
* Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.</li>
* <li>K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.</li>
* <li>Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.</li>
* </ol>
* </p>
* <p>
* From the original documentation:
* </p>
* <p>
* This routine calculates the LOG(GAMMA) function for a positive real argument X.
* Computation is based on an algorithm outlined in references 1 and 2.
* The program uses rational functions that theoretically approximate LOG(GAMMA)
* to at least 18 significant decimal digits. The approximation for X > 12 is from
* reference 3, while approximations for X < 12.0 are similar to those in reference
* 1, but are unpublished. The accuracy achieved depends on the arithmetic system,
* the compiler, the intrinsic functions, and proper selection of the
* machine-dependent constants.
* </p>
* <p>
* Error returns: <br />
* The program returns the value XINF for X .LE. 0.0 or when overflow would occur.
* The computation is believed to be free of underflow and overflow.
* </p>
*
* @version 1.1
*
* @author Jaco van Kooten
*
* @return float MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305
*/
public static function logGamma(float $x): float
{
if ($x == self::$logGammaCacheX) {
return self::$logGammaCacheResult;
}
$y = $x;
if ($y > 0.0 && $y <= self::LOG_GAMMA_X_MAX_VALUE) {
if ($y <= self::EPS) {
$res = -log($y);
} elseif ($y <= 1.5) {
$res = self::logGamma1($y);
} elseif ($y <= 4.0) {
$res = self::logGamma2($y);
} elseif ($y <= 12.0) {
$res = self::logGamma3($y);
} else {
$res = self::logGamma4($y);
}
} else {
// --------------------------
// Return for bad arguments
// --------------------------
$res = self::MAX_VALUE;
}
// ------------------------------
// Final adjustments and return
// ------------------------------
self::$logGammaCacheX = $x;
self::$logGammaCacheResult = $res;
return $res;
}
private static function logGamma1(float $y): float
{
// ---------------------
// EPS .LT. X .LE. 1.5
// ---------------------
if ($y < self::PNT68) {
$corr = -log($y);
$xm1 = $y;
} else {
$corr = 0.0;
$xm1 = $y - 1.0;
}
$xden = 1.0;
$xnum = 0.0;
if ($y <= 0.5 || $y >= self::PNT68) {
for ($i = 0; $i < 8; ++$i) {
$xnum = $xnum * $xm1 + self::LG_P1[$i];
$xden = $xden * $xm1 + self::LG_Q1[$i];
}
return $corr + $xm1 * (self::LG_D1 + $xm1 * ($xnum / $xden));
}
$xm2 = $y - 1.0;
for ($i = 0; $i < 8; ++$i) {
$xnum = $xnum * $xm2 + self::LG_P2[$i];
$xden = $xden * $xm2 + self::LG_Q2[$i];
}
return $corr + $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden));
}
private static function logGamma2(float $y): float
{
// ---------------------
// 1.5 .LT. X .LE. 4.0
// ---------------------
$xm2 = $y - 2.0;
$xden = 1.0;
$xnum = 0.0;
for ($i = 0; $i < 8; ++$i) {
$xnum = $xnum * $xm2 + self::LG_P2[$i];
$xden = $xden * $xm2 + self::LG_Q2[$i];
}
return $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden));
}
protected static function logGamma3(float $y): float
{
// ----------------------
// 4.0 .LT. X .LE. 12.0
// ----------------------
$xm4 = $y - 4.0;
$xden = -1.0;
$xnum = 0.0;
for ($i = 0; $i < 8; ++$i) {
$xnum = $xnum * $xm4 + self::LG_P4[$i];
$xden = $xden * $xm4 + self::LG_Q4[$i];
}
return self::LG_D4 + $xm4 * ($xnum / $xden);
}
protected static function logGamma4(float $y): float
{
// ---------------------------------
// Evaluate for argument .GE. 12.0
// ---------------------------------
$res = 0.0;
if ($y <= self::LG_FRTBIG) {
$res = self::LG_C[6];
$ysq = $y * $y;
for ($i = 0; $i < 6; ++$i) {
$res = $res / $ysq + self::LG_C[$i];
}
$res /= $y;
$corr = log($y);
$res = $res + log(self::SQRT2PI) - 0.5 * $corr;
$res += $y * ($corr - 1.0);
}
return $res;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Counts.php 0000644 00000005210 15167673464 0022020 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Counts extends AggregateBase
{
/**
* COUNT.
*
* Counts the number of cells that contain numbers within the list of arguments
*
* Excel Function:
* COUNT(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function COUNT(mixed ...$args): int
{
$returnValue = 0;
// Loop through arguments
$aArgs = Functions::flattenArrayIndexed($args);
foreach ($aArgs as $k => $arg) {
$arg = self::testAcceptedBoolean($arg, $k);
// Is it a numeric value?
// Strings containing numeric values are only counted if they are string literals (not cell values)
// and then only in MS Excel and in Open Office, not in Gnumeric
if (self::isAcceptedCountable($arg, $k, true)) {
++$returnValue;
}
}
return $returnValue;
}
/**
* COUNTA.
*
* Counts the number of cells that are not empty within the list of arguments
*
* Excel Function:
* COUNTA(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function COUNTA(mixed ...$args): int
{
$returnValue = 0;
// Loop through arguments
$aArgs = Functions::flattenArrayIndexed($args);
foreach ($aArgs as $k => $arg) {
// Nulls are counted if literals, but not if cell values
if ($arg !== null || (!Functions::isCellValue($k))) {
++$returnValue;
}
}
return $returnValue;
}
/**
* COUNTBLANK.
*
* Counts the number of empty cells within the list of arguments
*
* Excel Function:
* COUNTBLANK(value1[,value2[, ...]])
*
* @param mixed $range Data values
*/
public static function COUNTBLANK(mixed $range): int
{
if ($range === null) {
return 1;
}
if (!is_array($range) || array_key_exists(0, $range)) {
throw new CalcException('Must specify range of cells, not any kind of literal');
}
$returnValue = 0;
// Loop through arguments
$aArgs = Functions::flattenArray($range);
foreach ($aArgs as $arg) {
// Is it a blank cell?
if (($arg === null) || ((is_string($arg)) && ($arg == ''))) {
++$returnValue;
}
}
return $returnValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php 0000644 00000001536 15167673464 0025236 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class StatisticalValidations
{
public static function validateFloat(mixed $value): float
{
if (!is_numeric($value)) {
throw new Exception(ExcelError::VALUE());
}
return (float) $value;
}
public static function validateInt(mixed $value): int
{
if (!is_numeric($value)) {
throw new Exception(ExcelError::VALUE());
}
return (int) floor((float) $value);
}
public static function validateBool(mixed $value): bool
{
if (!is_bool($value) && !is_numeric($value)) {
throw new Exception(ExcelError::VALUE());
}
return (bool) $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Size.php 0000644 00000004720 15167673464 0021464 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Size
{
/**
* LARGE.
*
* Returns the nth largest value in a data set. You can use this function to
* select a value based on its relative standing.
*
* Excel Function:
* LARGE(value1[,value2[, ...]],entry)
*
* @param mixed $args Data values
*
* @return float|string The result, or a string containing an error
*/
public static function large(mixed ...$args)
{
$aArgs = Functions::flattenArray($args);
$entry = array_pop($aArgs);
if ((is_numeric($entry)) && (!is_string($entry))) {
$entry = (int) floor($entry);
$mArgs = self::filter($aArgs);
$count = Counts::COUNT($mArgs);
--$entry;
if ($count === 0 || $entry < 0 || $entry >= $count) {
return ExcelError::NAN();
}
rsort($mArgs);
return $mArgs[$entry];
}
return ExcelError::VALUE();
}
/**
* SMALL.
*
* Returns the nth smallest value in a data set. You can use this function to
* select a value based on its relative standing.
*
* Excel Function:
* SMALL(value1[,value2[, ...]],entry)
*
* @param mixed $args Data values
*
* @return float|string The result, or a string containing an error
*/
public static function small(mixed ...$args)
{
$aArgs = Functions::flattenArray($args);
$entry = array_pop($aArgs);
if ((is_numeric($entry)) && (!is_string($entry))) {
$entry = (int) floor($entry);
$mArgs = self::filter($aArgs);
$count = Counts::COUNT($mArgs);
--$entry;
if ($count === 0 || $entry < 0 || $entry >= $count) {
return ExcelError::NAN();
}
sort($mArgs);
return $mArgs[$entry];
}
return ExcelError::VALUE();
}
/**
* @param mixed[] $args Data values
*/
protected static function filter(array $args): array
{
$mArgs = [];
foreach ($args as $arg) {
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
$mArgs[] = $arg;
}
}
return $mArgs;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php 0000644 00000011534 15167673464 0020620 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentHelper;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentProcessor;
trait ArrayEnabled
{
private static bool $initializationNeeded = true;
private static ArrayArgumentHelper $arrayArgumentHelper;
/**
* @param array|false $arguments Can be changed to array for Php8.1+
*/
private static function initialiseHelper($arguments): void
{
if (self::$initializationNeeded === true) {
self::$arrayArgumentHelper = new ArrayArgumentHelper();
self::$initializationNeeded = false;
}
self::$arrayArgumentHelper->initialise(($arguments === false) ? [] : $arguments);
}
/**
* Handles array argument processing when the function accepts a single argument that can be an array argument.
* Example use for:
* DAYOFMONTH() or FACT().
*/
protected static function evaluateSingleArgumentArray(callable $method, array $values): array
{
$result = [];
foreach ($values as $value) {
$result[] = $method($value);
}
return $result;
}
/**
* Handles array argument processing when the function accepts multiple arguments,
* and any of them can be an array argument.
* Example use for:
* ROUND() or DATE().
*/
protected static function evaluateArrayArguments(callable $method, mixed ...$arguments): array
{
self::initialiseHelper($arguments);
$arguments = self::$arrayArgumentHelper->arguments();
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
}
/**
* Handles array argument processing when the function accepts multiple arguments,
* but only the first few (up to limit) can be an array arguments.
* Example use for:
* NETWORKDAYS() or CONCATENATE(), where the last argument is a matrix (or a series of values) that need
* to be treated as a such rather than as an array arguments.
*/
protected static function evaluateArrayArgumentsSubset(callable $method, int $limit, mixed ...$arguments): array
{
self::initialiseHelper(array_slice($arguments, 0, $limit));
$trailingArguments = array_slice($arguments, $limit);
$arguments = self::$arrayArgumentHelper->arguments();
$arguments = array_merge($arguments, $trailingArguments);
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
}
private static function testFalse(mixed $value): bool
{
return $value === false;
}
/**
* Handles array argument processing when the function accepts multiple arguments,
* but only the last few (from start) can be an array arguments.
* Example use for:
* Z.TEST() or INDEX(), where the first argument 1 is a matrix that needs to be treated as a dataset
* rather than as an array argument.
*/
protected static function evaluateArrayArgumentsSubsetFrom(callable $method, int $start, mixed ...$arguments): array
{
$arrayArgumentsSubset = array_combine(
range($start, count($arguments) - $start),
array_slice($arguments, $start)
);
if (self::testFalse($arrayArgumentsSubset)) {
return ['#VALUE!'];
}
self::initialiseHelper($arrayArgumentsSubset);
$leadingArguments = array_slice($arguments, 0, $start);
$arguments = self::$arrayArgumentHelper->arguments();
$arguments = array_merge($leadingArguments, $arguments);
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
}
/**
* Handles array argument processing when the function accepts multiple arguments,
* and any of them can be an array argument except for the one specified by ignore.
* Example use for:
* HLOOKUP() and VLOOKUP(), where argument 1 is a matrix that needs to be treated as a database
* rather than as an array argument.
*/
protected static function evaluateArrayArgumentsIgnore(callable $method, int $ignore, mixed ...$arguments): array
{
$leadingArguments = array_slice($arguments, 0, $ignore);
$ignoreArgument = array_slice($arguments, $ignore, 1);
$trailingArguments = array_slice($arguments, $ignore + 1);
self::initialiseHelper(array_merge($leadingArguments, [[null]], $trailingArguments));
$arguments = self::$arrayArgumentHelper->arguments();
array_splice($arguments, $ignore, 1, $ignoreArgument);
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php 0000644 00000001321 15167673464 0020035 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
abstract class Category
{
// Function categories
const CATEGORY_CUBE = 'Cube';
const CATEGORY_DATABASE = 'Database';
const CATEGORY_DATE_AND_TIME = 'Date and Time';
const CATEGORY_ENGINEERING = 'Engineering';
const CATEGORY_FINANCIAL = 'Financial';
const CATEGORY_INFORMATION = 'Information';
const CATEGORY_LOGICAL = 'Logical';
const CATEGORY_LOOKUP_AND_REFERENCE = 'Lookup and Reference';
const CATEGORY_MATH_AND_TRIG = 'Math and Trig';
const CATEGORY_STATISTICAL = 'Statistical';
const CATEGORY_TEXT_AND_DATA = 'Text and Data';
const CATEGORY_WEB = 'Web';
const CATEGORY_UNCATEGORISED = 'Uncategorised';
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php 0000644 00000003171 15167673464 0021723 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class SeriesSum
{
use ArrayEnabled;
/**
* SERIESSUM.
*
* Returns the sum of a power series
*
* @param mixed $x Input value
* @param mixed $n Initial power
* @param mixed $m Step
* @param mixed[] $args An array of coefficients for the Data Series
*
* @return array|float|int|string The result, or a string containing an error
*/
public static function evaluate(mixed $x, mixed $n, mixed $m, ...$args): array|string|float|int
{
if (is_array($x) || is_array($n) || is_array($m)) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 3, $x, $n, $m, ...$args);
}
try {
$x = Helpers::validateNumericNullSubstitution($x, 0);
$n = Helpers::validateNumericNullSubstitution($n, 0);
$m = Helpers::validateNumericNullSubstitution($m, 0);
// Loop through arguments
$aArgs = Functions::flattenArray($args);
$returnValue = 0;
$i = 0;
foreach ($aArgs as $argx) {
if ($argx !== null) {
$arg = Helpers::validateNumericNullSubstitution($argx, 0);
$returnValue += $arg * $x ** ($n + ($m * $i));
++$i;
}
}
} catch (Exception $e) {
return $e->getMessage();
}
return $returnValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Base.php 0000644 00000004351 15167673464 0020657 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Base
{
use ArrayEnabled;
/**
* BASE.
*
* Converts a number into a text representation with the given radix (base).
*
* Excel Function:
* BASE(Number, Radix [Min_length])
*
* @param mixed $number expect float
* Or can be an array of values
* @param mixed $radix expect float
* Or can be an array of values
* @param mixed $minLength expect int or null
* Or can be an array of values
*
* @return array|string the text representation with the given radix (base)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function evaluate(mixed $number, mixed $radix, mixed $minLength = null): array|string
{
if (is_array($number) || is_array($radix) || is_array($minLength)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $radix, $minLength);
}
try {
$number = (float) floor(Helpers::validateNumericNullBool($number));
$radix = (int) Helpers::validateNumericNullBool($radix);
} catch (Exception $e) {
return $e->getMessage();
}
return self::calculate($number, $radix, $minLength);
}
private static function calculate(float $number, int $radix, mixed $minLength): string
{
if ($minLength === null || is_numeric($minLength)) {
if ($number < 0 || $number >= 2 ** 53 || $radix < 2 || $radix > 36) {
return ExcelError::NAN(); // Numeric range constraints
}
$outcome = strtoupper((string) base_convert("$number", 10, $radix));
if ($minLength !== null) {
$outcome = str_pad($outcome, (int) $minLength, '0', STR_PAD_LEFT); // String padding
}
return $outcome;
}
return ExcelError::VALUE();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php 0000644 00000064151 15167673464 0021065 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Roman
{
use ArrayEnabled;
private const VALUES = [
45 => ['VL'],
46 => ['VLI'],
47 => ['VLII'],
48 => ['VLIII'],
49 => ['VLIV', 'IL'],
95 => ['VC'],
96 => ['VCI'],
97 => ['VCII'],
98 => ['VCIII'],
99 => ['VCIV', 'IC'],
145 => ['CVL'],
146 => ['CVLI'],
147 => ['CVLII'],
148 => ['CVLIII'],
149 => ['CVLIV', 'CIL'],
195 => ['CVC'],
196 => ['CVCI'],
197 => ['CVCII'],
198 => ['CVCIII'],
199 => ['CVCIV', 'CIC'],
245 => ['CCVL'],
246 => ['CCVLI'],
247 => ['CCVLII'],
248 => ['CCVLIII'],
249 => ['CCVLIV', 'CCIL'],
295 => ['CCVC'],
296 => ['CCVCI'],
297 => ['CCVCII'],
298 => ['CCVCIII'],
299 => ['CCVCIV', 'CCIC'],
345 => ['CCCVL'],
346 => ['CCCVLI'],
347 => ['CCCVLII'],
348 => ['CCCVLIII'],
349 => ['CCCVLIV', 'CCCIL'],
395 => ['CCCVC'],
396 => ['CCCVCI'],
397 => ['CCCVCII'],
398 => ['CCCVCIII'],
399 => ['CCCVCIV', 'CCCIC'],
445 => ['CDVL'],
446 => ['CDVLI'],
447 => ['CDVLII'],
448 => ['CDVLIII'],
449 => ['CDVLIV', 'CDIL'],
450 => ['LD'],
451 => ['LDI'],
452 => ['LDII'],
453 => ['LDIII'],
454 => ['LDIV'],
455 => ['LDV'],
456 => ['LDVI'],
457 => ['LDVII'],
458 => ['LDVIII'],
459 => ['LDIX'],
460 => ['LDX'],
461 => ['LDXI'],
462 => ['LDXII'],
463 => ['LDXIII'],
464 => ['LDXIV'],
465 => ['LDXV'],
466 => ['LDXVI'],
467 => ['LDXVII'],
468 => ['LDXVIII'],
469 => ['LDXIX'],
470 => ['LDXX'],
471 => ['LDXXI'],
472 => ['LDXXII'],
473 => ['LDXXIII'],
474 => ['LDXXIV'],
475 => ['LDXXV'],
476 => ['LDXXVI'],
477 => ['LDXXVII'],
478 => ['LDXXVIII'],
479 => ['LDXXIX'],
480 => ['LDXXX'],
481 => ['LDXXXI'],
482 => ['LDXXXII'],
483 => ['LDXXXIII'],
484 => ['LDXXXIV'],
485 => ['LDXXXV'],
486 => ['LDXXXVI'],
487 => ['LDXXXVII'],
488 => ['LDXXXVIII'],
489 => ['LDXXXIX'],
490 => ['LDXL', 'XD'],
491 => ['LDXLI', 'XDI'],
492 => ['LDXLII', 'XDII'],
493 => ['LDXLIII', 'XDIII'],
494 => ['LDXLIV', 'XDIV'],
495 => ['LDVL', 'XDV', 'VD'],
496 => ['LDVLI', 'XDVI', 'VDI'],
497 => ['LDVLII', 'XDVII', 'VDII'],
498 => ['LDVLIII', 'XDVIII', 'VDIII'],
499 => ['LDVLIV', 'XDIX', 'VDIV', 'ID'],
545 => ['DVL'],
546 => ['DVLI'],
547 => ['DVLII'],
548 => ['DVLIII'],
549 => ['DVLIV', 'DIL'],
595 => ['DVC'],
596 => ['DVCI'],
597 => ['DVCII'],
598 => ['DVCIII'],
599 => ['DVCIV', 'DIC'],
645 => ['DCVL'],
646 => ['DCVLI'],
647 => ['DCVLII'],
648 => ['DCVLIII'],
649 => ['DCVLIV', 'DCIL'],
695 => ['DCVC'],
696 => ['DCVCI'],
697 => ['DCVCII'],
698 => ['DCVCIII'],
699 => ['DCVCIV', 'DCIC'],
745 => ['DCCVL'],
746 => ['DCCVLI'],
747 => ['DCCVLII'],
748 => ['DCCVLIII'],
749 => ['DCCVLIV', 'DCCIL'],
795 => ['DCCVC'],
796 => ['DCCVCI'],
797 => ['DCCVCII'],
798 => ['DCCVCIII'],
799 => ['DCCVCIV', 'DCCIC'],
845 => ['DCCCVL'],
846 => ['DCCCVLI'],
847 => ['DCCCVLII'],
848 => ['DCCCVLIII'],
849 => ['DCCCVLIV', 'DCCCIL'],
895 => ['DCCCVC'],
896 => ['DCCCVCI'],
897 => ['DCCCVCII'],
898 => ['DCCCVCIII'],
899 => ['DCCCVCIV', 'DCCCIC'],
945 => ['CMVL'],
946 => ['CMVLI'],
947 => ['CMVLII'],
948 => ['CMVLIII'],
949 => ['CMVLIV', 'CMIL'],
950 => ['LM'],
951 => ['LMI'],
952 => ['LMII'],
953 => ['LMIII'],
954 => ['LMIV'],
955 => ['LMV'],
956 => ['LMVI'],
957 => ['LMVII'],
958 => ['LMVIII'],
959 => ['LMIX'],
960 => ['LMX'],
961 => ['LMXI'],
962 => ['LMXII'],
963 => ['LMXIII'],
964 => ['LMXIV'],
965 => ['LMXV'],
966 => ['LMXVI'],
967 => ['LMXVII'],
968 => ['LMXVIII'],
969 => ['LMXIX'],
970 => ['LMXX'],
971 => ['LMXXI'],
972 => ['LMXXII'],
973 => ['LMXXIII'],
974 => ['LMXXIV'],
975 => ['LMXXV'],
976 => ['LMXXVI'],
977 => ['LMXXVII'],
978 => ['LMXXVIII'],
979 => ['LMXXIX'],
980 => ['LMXXX'],
981 => ['LMXXXI'],
982 => ['LMXXXII'],
983 => ['LMXXXIII'],
984 => ['LMXXXIV'],
985 => ['LMXXXV'],
986 => ['LMXXXVI'],
987 => ['LMXXXVII'],
988 => ['LMXXXVIII'],
989 => ['LMXXXIX'],
990 => ['LMXL', 'XM'],
991 => ['LMXLI', 'XMI'],
992 => ['LMXLII', 'XMII'],
993 => ['LMXLIII', 'XMIII'],
994 => ['LMXLIV', 'XMIV'],
995 => ['LMVL', 'XMV', 'VM'],
996 => ['LMVLI', 'XMVI', 'VMI'],
997 => ['LMVLII', 'XMVII', 'VMII'],
998 => ['LMVLIII', 'XMVIII', 'VMIII'],
999 => ['LMVLIV', 'XMIX', 'VMIV', 'IM'],
1045 => ['MVL'],
1046 => ['MVLI'],
1047 => ['MVLII'],
1048 => ['MVLIII'],
1049 => ['MVLIV', 'MIL'],
1095 => ['MVC'],
1096 => ['MVCI'],
1097 => ['MVCII'],
1098 => ['MVCIII'],
1099 => ['MVCIV', 'MIC'],
1145 => ['MCVL'],
1146 => ['MCVLI'],
1147 => ['MCVLII'],
1148 => ['MCVLIII'],
1149 => ['MCVLIV', 'MCIL'],
1195 => ['MCVC'],
1196 => ['MCVCI'],
1197 => ['MCVCII'],
1198 => ['MCVCIII'],
1199 => ['MCVCIV', 'MCIC'],
1245 => ['MCCVL'],
1246 => ['MCCVLI'],
1247 => ['MCCVLII'],
1248 => ['MCCVLIII'],
1249 => ['MCCVLIV', 'MCCIL'],
1295 => ['MCCVC'],
1296 => ['MCCVCI'],
1297 => ['MCCVCII'],
1298 => ['MCCVCIII'],
1299 => ['MCCVCIV', 'MCCIC'],
1345 => ['MCCCVL'],
1346 => ['MCCCVLI'],
1347 => ['MCCCVLII'],
1348 => ['MCCCVLIII'],
1349 => ['MCCCVLIV', 'MCCCIL'],
1395 => ['MCCCVC'],
1396 => ['MCCCVCI'],
1397 => ['MCCCVCII'],
1398 => ['MCCCVCIII'],
1399 => ['MCCCVCIV', 'MCCCIC'],
1445 => ['MCDVL'],
1446 => ['MCDVLI'],
1447 => ['MCDVLII'],
1448 => ['MCDVLIII'],
1449 => ['MCDVLIV', 'MCDIL'],
1450 => ['MLD'],
1451 => ['MLDI'],
1452 => ['MLDII'],
1453 => ['MLDIII'],
1454 => ['MLDIV'],
1455 => ['MLDV'],
1456 => ['MLDVI'],
1457 => ['MLDVII'],
1458 => ['MLDVIII'],
1459 => ['MLDIX'],
1460 => ['MLDX'],
1461 => ['MLDXI'],
1462 => ['MLDXII'],
1463 => ['MLDXIII'],
1464 => ['MLDXIV'],
1465 => ['MLDXV'],
1466 => ['MLDXVI'],
1467 => ['MLDXVII'],
1468 => ['MLDXVIII'],
1469 => ['MLDXIX'],
1470 => ['MLDXX'],
1471 => ['MLDXXI'],
1472 => ['MLDXXII'],
1473 => ['MLDXXIII'],
1474 => ['MLDXXIV'],
1475 => ['MLDXXV'],
1476 => ['MLDXXVI'],
1477 => ['MLDXXVII'],
1478 => ['MLDXXVIII'],
1479 => ['MLDXXIX'],
1480 => ['MLDXXX'],
1481 => ['MLDXXXI'],
1482 => ['MLDXXXII'],
1483 => ['MLDXXXIII'],
1484 => ['MLDXXXIV'],
1485 => ['MLDXXXV'],
1486 => ['MLDXXXVI'],
1487 => ['MLDXXXVII'],
1488 => ['MLDXXXVIII'],
1489 => ['MLDXXXIX'],
1490 => ['MLDXL', 'MXD'],
1491 => ['MLDXLI', 'MXDI'],
1492 => ['MLDXLII', 'MXDII'],
1493 => ['MLDXLIII', 'MXDIII'],
1494 => ['MLDXLIV', 'MXDIV'],
1495 => ['MLDVL', 'MXDV', 'MVD'],
1496 => ['MLDVLI', 'MXDVI', 'MVDI'],
1497 => ['MLDVLII', 'MXDVII', 'MVDII'],
1498 => ['MLDVLIII', 'MXDVIII', 'MVDIII'],
1499 => ['MLDVLIV', 'MXDIX', 'MVDIV', 'MID'],
1545 => ['MDVL'],
1546 => ['MDVLI'],
1547 => ['MDVLII'],
1548 => ['MDVLIII'],
1549 => ['MDVLIV', 'MDIL'],
1595 => ['MDVC'],
1596 => ['MDVCI'],
1597 => ['MDVCII'],
1598 => ['MDVCIII'],
1599 => ['MDVCIV', 'MDIC'],
1645 => ['MDCVL'],
1646 => ['MDCVLI'],
1647 => ['MDCVLII'],
1648 => ['MDCVLIII'],
1649 => ['MDCVLIV', 'MDCIL'],
1695 => ['MDCVC'],
1696 => ['MDCVCI'],
1697 => ['MDCVCII'],
1698 => ['MDCVCIII'],
1699 => ['MDCVCIV', 'MDCIC'],
1745 => ['MDCCVL'],
1746 => ['MDCCVLI'],
1747 => ['MDCCVLII'],
1748 => ['MDCCVLIII'],
1749 => ['MDCCVLIV', 'MDCCIL'],
1795 => ['MDCCVC'],
1796 => ['MDCCVCI'],
1797 => ['MDCCVCII'],
1798 => ['MDCCVCIII'],
1799 => ['MDCCVCIV', 'MDCCIC'],
1845 => ['MDCCCVL'],
1846 => ['MDCCCVLI'],
1847 => ['MDCCCVLII'],
1848 => ['MDCCCVLIII'],
1849 => ['MDCCCVLIV', 'MDCCCIL'],
1895 => ['MDCCCVC'],
1896 => ['MDCCCVCI'],
1897 => ['MDCCCVCII'],
1898 => ['MDCCCVCIII'],
1899 => ['MDCCCVCIV', 'MDCCCIC'],
1945 => ['MCMVL'],
1946 => ['MCMVLI'],
1947 => ['MCMVLII'],
1948 => ['MCMVLIII'],
1949 => ['MCMVLIV', 'MCMIL'],
1950 => ['MLM'],
1951 => ['MLMI'],
1952 => ['MLMII'],
1953 => ['MLMIII'],
1954 => ['MLMIV'],
1955 => ['MLMV'],
1956 => ['MLMVI'],
1957 => ['MLMVII'],
1958 => ['MLMVIII'],
1959 => ['MLMIX'],
1960 => ['MLMX'],
1961 => ['MLMXI'],
1962 => ['MLMXII'],
1963 => ['MLMXIII'],
1964 => ['MLMXIV'],
1965 => ['MLMXV'],
1966 => ['MLMXVI'],
1967 => ['MLMXVII'],
1968 => ['MLMXVIII'],
1969 => ['MLMXIX'],
1970 => ['MLMXX'],
1971 => ['MLMXXI'],
1972 => ['MLMXXII'],
1973 => ['MLMXXIII'],
1974 => ['MLMXXIV'],
1975 => ['MLMXXV'],
1976 => ['MLMXXVI'],
1977 => ['MLMXXVII'],
1978 => ['MLMXXVIII'],
1979 => ['MLMXXIX'],
1980 => ['MLMXXX'],
1981 => ['MLMXXXI'],
1982 => ['MLMXXXII'],
1983 => ['MLMXXXIII'],
1984 => ['MLMXXXIV'],
1985 => ['MLMXXXV'],
1986 => ['MLMXXXVI'],
1987 => ['MLMXXXVII'],
1988 => ['MLMXXXVIII'],
1989 => ['MLMXXXIX'],
1990 => ['MLMXL', 'MXM'],
1991 => ['MLMXLI', 'MXMI'],
1992 => ['MLMXLII', 'MXMII'],
1993 => ['MLMXLIII', 'MXMIII'],
1994 => ['MLMXLIV', 'MXMIV'],
1995 => ['MLMVL', 'MXMV', 'MVM'],
1996 => ['MLMVLI', 'MXMVI', 'MVMI'],
1997 => ['MLMVLII', 'MXMVII', 'MVMII'],
1998 => ['MLMVLIII', 'MXMVIII', 'MVMIII'],
1999 => ['MLMVLIV', 'MXMIX', 'MVMIV', 'MIM'],
2045 => ['MMVL'],
2046 => ['MMVLI'],
2047 => ['MMVLII'],
2048 => ['MMVLIII'],
2049 => ['MMVLIV', 'MMIL'],
2095 => ['MMVC'],
2096 => ['MMVCI'],
2097 => ['MMVCII'],
2098 => ['MMVCIII'],
2099 => ['MMVCIV', 'MMIC'],
2145 => ['MMCVL'],
2146 => ['MMCVLI'],
2147 => ['MMCVLII'],
2148 => ['MMCVLIII'],
2149 => ['MMCVLIV', 'MMCIL'],
2195 => ['MMCVC'],
2196 => ['MMCVCI'],
2197 => ['MMCVCII'],
2198 => ['MMCVCIII'],
2199 => ['MMCVCIV', 'MMCIC'],
2245 => ['MMCCVL'],
2246 => ['MMCCVLI'],
2247 => ['MMCCVLII'],
2248 => ['MMCCVLIII'],
2249 => ['MMCCVLIV', 'MMCCIL'],
2295 => ['MMCCVC'],
2296 => ['MMCCVCI'],
2297 => ['MMCCVCII'],
2298 => ['MMCCVCIII'],
2299 => ['MMCCVCIV', 'MMCCIC'],
2345 => ['MMCCCVL'],
2346 => ['MMCCCVLI'],
2347 => ['MMCCCVLII'],
2348 => ['MMCCCVLIII'],
2349 => ['MMCCCVLIV', 'MMCCCIL'],
2395 => ['MMCCCVC'],
2396 => ['MMCCCVCI'],
2397 => ['MMCCCVCII'],
2398 => ['MMCCCVCIII'],
2399 => ['MMCCCVCIV', 'MMCCCIC'],
2445 => ['MMCDVL'],
2446 => ['MMCDVLI'],
2447 => ['MMCDVLII'],
2448 => ['MMCDVLIII'],
2449 => ['MMCDVLIV', 'MMCDIL'],
2450 => ['MMLD'],
2451 => ['MMLDI'],
2452 => ['MMLDII'],
2453 => ['MMLDIII'],
2454 => ['MMLDIV'],
2455 => ['MMLDV'],
2456 => ['MMLDVI'],
2457 => ['MMLDVII'],
2458 => ['MMLDVIII'],
2459 => ['MMLDIX'],
2460 => ['MMLDX'],
2461 => ['MMLDXI'],
2462 => ['MMLDXII'],
2463 => ['MMLDXIII'],
2464 => ['MMLDXIV'],
2465 => ['MMLDXV'],
2466 => ['MMLDXVI'],
2467 => ['MMLDXVII'],
2468 => ['MMLDXVIII'],
2469 => ['MMLDXIX'],
2470 => ['MMLDXX'],
2471 => ['MMLDXXI'],
2472 => ['MMLDXXII'],
2473 => ['MMLDXXIII'],
2474 => ['MMLDXXIV'],
2475 => ['MMLDXXV'],
2476 => ['MMLDXXVI'],
2477 => ['MMLDXXVII'],
2478 => ['MMLDXXVIII'],
2479 => ['MMLDXXIX'],
2480 => ['MMLDXXX'],
2481 => ['MMLDXXXI'],
2482 => ['MMLDXXXII'],
2483 => ['MMLDXXXIII'],
2484 => ['MMLDXXXIV'],
2485 => ['MMLDXXXV'],
2486 => ['MMLDXXXVI'],
2487 => ['MMLDXXXVII'],
2488 => ['MMLDXXXVIII'],
2489 => ['MMLDXXXIX'],
2490 => ['MMLDXL', 'MMXD'],
2491 => ['MMLDXLI', 'MMXDI'],
2492 => ['MMLDXLII', 'MMXDII'],
2493 => ['MMLDXLIII', 'MMXDIII'],
2494 => ['MMLDXLIV', 'MMXDIV'],
2495 => ['MMLDVL', 'MMXDV', 'MMVD'],
2496 => ['MMLDVLI', 'MMXDVI', 'MMVDI'],
2497 => ['MMLDVLII', 'MMXDVII', 'MMVDII'],
2498 => ['MMLDVLIII', 'MMXDVIII', 'MMVDIII'],
2499 => ['MMLDVLIV', 'MMXDIX', 'MMVDIV', 'MMID'],
2545 => ['MMDVL'],
2546 => ['MMDVLI'],
2547 => ['MMDVLII'],
2548 => ['MMDVLIII'],
2549 => ['MMDVLIV', 'MMDIL'],
2595 => ['MMDVC'],
2596 => ['MMDVCI'],
2597 => ['MMDVCII'],
2598 => ['MMDVCIII'],
2599 => ['MMDVCIV', 'MMDIC'],
2645 => ['MMDCVL'],
2646 => ['MMDCVLI'],
2647 => ['MMDCVLII'],
2648 => ['MMDCVLIII'],
2649 => ['MMDCVLIV', 'MMDCIL'],
2695 => ['MMDCVC'],
2696 => ['MMDCVCI'],
2697 => ['MMDCVCII'],
2698 => ['MMDCVCIII'],
2699 => ['MMDCVCIV', 'MMDCIC'],
2745 => ['MMDCCVL'],
2746 => ['MMDCCVLI'],
2747 => ['MMDCCVLII'],
2748 => ['MMDCCVLIII'],
2749 => ['MMDCCVLIV', 'MMDCCIL'],
2795 => ['MMDCCVC'],
2796 => ['MMDCCVCI'],
2797 => ['MMDCCVCII'],
2798 => ['MMDCCVCIII'],
2799 => ['MMDCCVCIV', 'MMDCCIC'],
2845 => ['MMDCCCVL'],
2846 => ['MMDCCCVLI'],
2847 => ['MMDCCCVLII'],
2848 => ['MMDCCCVLIII'],
2849 => ['MMDCCCVLIV', 'MMDCCCIL'],
2895 => ['MMDCCCVC'],
2896 => ['MMDCCCVCI'],
2897 => ['MMDCCCVCII'],
2898 => ['MMDCCCVCIII'],
2899 => ['MMDCCCVCIV', 'MMDCCCIC'],
2945 => ['MMCMVL'],
2946 => ['MMCMVLI'],
2947 => ['MMCMVLII'],
2948 => ['MMCMVLIII'],
2949 => ['MMCMVLIV', 'MMCMIL'],
2950 => ['MMLM'],
2951 => ['MMLMI'],
2952 => ['MMLMII'],
2953 => ['MMLMIII'],
2954 => ['MMLMIV'],
2955 => ['MMLMV'],
2956 => ['MMLMVI'],
2957 => ['MMLMVII'],
2958 => ['MMLMVIII'],
2959 => ['MMLMIX'],
2960 => ['MMLMX'],
2961 => ['MMLMXI'],
2962 => ['MMLMXII'],
2963 => ['MMLMXIII'],
2964 => ['MMLMXIV'],
2965 => ['MMLMXV'],
2966 => ['MMLMXVI'],
2967 => ['MMLMXVII'],
2968 => ['MMLMXVIII'],
2969 => ['MMLMXIX'],
2970 => ['MMLMXX'],
2971 => ['MMLMXXI'],
2972 => ['MMLMXXII'],
2973 => ['MMLMXXIII'],
2974 => ['MMLMXXIV'],
2975 => ['MMLMXXV'],
2976 => ['MMLMXXVI'],
2977 => ['MMLMXXVII'],
2978 => ['MMLMXXVIII'],
2979 => ['MMLMXXIX'],
2980 => ['MMLMXXX'],
2981 => ['MMLMXXXI'],
2982 => ['MMLMXXXII'],
2983 => ['MMLMXXXIII'],
2984 => ['MMLMXXXIV'],
2985 => ['MMLMXXXV'],
2986 => ['MMLMXXXVI'],
2987 => ['MMLMXXXVII'],
2988 => ['MMLMXXXVIII'],
2989 => ['MMLMXXXIX'],
2990 => ['MMLMXL', 'MMXM'],
2991 => ['MMLMXLI', 'MMXMI'],
2992 => ['MMLMXLII', 'MMXMII'],
2993 => ['MMLMXLIII', 'MMXMIII'],
2994 => ['MMLMXLIV', 'MMXMIV'],
2995 => ['MMLMVL', 'MMXMV', 'MMVM'],
2996 => ['MMLMVLI', 'MMXMVI', 'MMVMI'],
2997 => ['MMLMVLII', 'MMXMVII', 'MMVMII'],
2998 => ['MMLMVLIII', 'MMXMVIII', 'MMVMIII'],
2999 => ['MMLMVLIV', 'MMXMIX', 'MMVMIV', 'MMIM'],
3045 => ['MMMVL'],
3046 => ['MMMVLI'],
3047 => ['MMMVLII'],
3048 => ['MMMVLIII'],
3049 => ['MMMVLIV', 'MMMIL'],
3095 => ['MMMVC'],
3096 => ['MMMVCI'],
3097 => ['MMMVCII'],
3098 => ['MMMVCIII'],
3099 => ['MMMVCIV', 'MMMIC'],
3145 => ['MMMCVL'],
3146 => ['MMMCVLI'],
3147 => ['MMMCVLII'],
3148 => ['MMMCVLIII'],
3149 => ['MMMCVLIV', 'MMMCIL'],
3195 => ['MMMCVC'],
3196 => ['MMMCVCI'],
3197 => ['MMMCVCII'],
3198 => ['MMMCVCIII'],
3199 => ['MMMCVCIV', 'MMMCIC'],
3245 => ['MMMCCVL'],
3246 => ['MMMCCVLI'],
3247 => ['MMMCCVLII'],
3248 => ['MMMCCVLIII'],
3249 => ['MMMCCVLIV', 'MMMCCIL'],
3295 => ['MMMCCVC'],
3296 => ['MMMCCVCI'],
3297 => ['MMMCCVCII'],
3298 => ['MMMCCVCIII'],
3299 => ['MMMCCVCIV', 'MMMCCIC'],
3345 => ['MMMCCCVL'],
3346 => ['MMMCCCVLI'],
3347 => ['MMMCCCVLII'],
3348 => ['MMMCCCVLIII'],
3349 => ['MMMCCCVLIV', 'MMMCCCIL'],
3395 => ['MMMCCCVC'],
3396 => ['MMMCCCVCI'],
3397 => ['MMMCCCVCII'],
3398 => ['MMMCCCVCIII'],
3399 => ['MMMCCCVCIV', 'MMMCCCIC'],
3445 => ['MMMCDVL'],
3446 => ['MMMCDVLI'],
3447 => ['MMMCDVLII'],
3448 => ['MMMCDVLIII'],
3449 => ['MMMCDVLIV', 'MMMCDIL'],
3450 => ['MMMLD'],
3451 => ['MMMLDI'],
3452 => ['MMMLDII'],
3453 => ['MMMLDIII'],
3454 => ['MMMLDIV'],
3455 => ['MMMLDV'],
3456 => ['MMMLDVI'],
3457 => ['MMMLDVII'],
3458 => ['MMMLDVIII'],
3459 => ['MMMLDIX'],
3460 => ['MMMLDX'],
3461 => ['MMMLDXI'],
3462 => ['MMMLDXII'],
3463 => ['MMMLDXIII'],
3464 => ['MMMLDXIV'],
3465 => ['MMMLDXV'],
3466 => ['MMMLDXVI'],
3467 => ['MMMLDXVII'],
3468 => ['MMMLDXVIII'],
3469 => ['MMMLDXIX'],
3470 => ['MMMLDXX'],
3471 => ['MMMLDXXI'],
3472 => ['MMMLDXXII'],
3473 => ['MMMLDXXIII'],
3474 => ['MMMLDXXIV'],
3475 => ['MMMLDXXV'],
3476 => ['MMMLDXXVI'],
3477 => ['MMMLDXXVII'],
3478 => ['MMMLDXXVIII'],
3479 => ['MMMLDXXIX'],
3480 => ['MMMLDXXX'],
3481 => ['MMMLDXXXI'],
3482 => ['MMMLDXXXII'],
3483 => ['MMMLDXXXIII'],
3484 => ['MMMLDXXXIV'],
3485 => ['MMMLDXXXV'],
3486 => ['MMMLDXXXVI'],
3487 => ['MMMLDXXXVII'],
3488 => ['MMMLDXXXVIII'],
3489 => ['MMMLDXXXIX'],
3490 => ['MMMLDXL', 'MMMXD'],
3491 => ['MMMLDXLI', 'MMMXDI'],
3492 => ['MMMLDXLII', 'MMMXDII'],
3493 => ['MMMLDXLIII', 'MMMXDIII'],
3494 => ['MMMLDXLIV', 'MMMXDIV'],
3495 => ['MMMLDVL', 'MMMXDV', 'MMMVD'],
3496 => ['MMMLDVLI', 'MMMXDVI', 'MMMVDI'],
3497 => ['MMMLDVLII', 'MMMXDVII', 'MMMVDII'],
3498 => ['MMMLDVLIII', 'MMMXDVIII', 'MMMVDIII'],
3499 => ['MMMLDVLIV', 'MMMXDIX', 'MMMVDIV', 'MMMID'],
3545 => ['MMMDVL'],
3546 => ['MMMDVLI'],
3547 => ['MMMDVLII'],
3548 => ['MMMDVLIII'],
3549 => ['MMMDVLIV', 'MMMDIL'],
3595 => ['MMMDVC'],
3596 => ['MMMDVCI'],
3597 => ['MMMDVCII'],
3598 => ['MMMDVCIII'],
3599 => ['MMMDVCIV', 'MMMDIC'],
3645 => ['MMMDCVL'],
3646 => ['MMMDCVLI'],
3647 => ['MMMDCVLII'],
3648 => ['MMMDCVLIII'],
3649 => ['MMMDCVLIV', 'MMMDCIL'],
3695 => ['MMMDCVC'],
3696 => ['MMMDCVCI'],
3697 => ['MMMDCVCII'],
3698 => ['MMMDCVCIII'],
3699 => ['MMMDCVCIV', 'MMMDCIC'],
3745 => ['MMMDCCVL'],
3746 => ['MMMDCCVLI'],
3747 => ['MMMDCCVLII'],
3748 => ['MMMDCCVLIII'],
3749 => ['MMMDCCVLIV', 'MMMDCCIL'],
3795 => ['MMMDCCVC'],
3796 => ['MMMDCCVCI'],
3797 => ['MMMDCCVCII'],
3798 => ['MMMDCCVCIII'],
3799 => ['MMMDCCVCIV', 'MMMDCCIC'],
3845 => ['MMMDCCCVL'],
3846 => ['MMMDCCCVLI'],
3847 => ['MMMDCCCVLII'],
3848 => ['MMMDCCCVLIII'],
3849 => ['MMMDCCCVLIV', 'MMMDCCCIL'],
3895 => ['MMMDCCCVC'],
3896 => ['MMMDCCCVCI'],
3897 => ['MMMDCCCVCII'],
3898 => ['MMMDCCCVCIII'],
3899 => ['MMMDCCCVCIV', 'MMMDCCCIC'],
3945 => ['MMMCMVL'],
3946 => ['MMMCMVLI'],
3947 => ['MMMCMVLII'],
3948 => ['MMMCMVLIII'],
3949 => ['MMMCMVLIV', 'MMMCMIL'],
3950 => ['MMMLM'],
3951 => ['MMMLMI'],
3952 => ['MMMLMII'],
3953 => ['MMMLMIII'],
3954 => ['MMMLMIV'],
3955 => ['MMMLMV'],
3956 => ['MMMLMVI'],
3957 => ['MMMLMVII'],
3958 => ['MMMLMVIII'],
3959 => ['MMMLMIX'],
3960 => ['MMMLMX'],
3961 => ['MMMLMXI'],
3962 => ['MMMLMXII'],
3963 => ['MMMLMXIII'],
3964 => ['MMMLMXIV'],
3965 => ['MMMLMXV'],
3966 => ['MMMLMXVI'],
3967 => ['MMMLMXVII'],
3968 => ['MMMLMXVIII'],
3969 => ['MMMLMXIX'],
3970 => ['MMMLMXX'],
3971 => ['MMMLMXXI'],
3972 => ['MMMLMXXII'],
3973 => ['MMMLMXXIII'],
3974 => ['MMMLMXXIV'],
3975 => ['MMMLMXXV'],
3976 => ['MMMLMXXVI'],
3977 => ['MMMLMXXVII'],
3978 => ['MMMLMXXVIII'],
3979 => ['MMMLMXXIX'],
3980 => ['MMMLMXXX'],
3981 => ['MMMLMXXXI'],
3982 => ['MMMLMXXXII'],
3983 => ['MMMLMXXXIII'],
3984 => ['MMMLMXXXIV'],
3985 => ['MMMLMXXXV'],
3986 => ['MMMLMXXXVI'],
3987 => ['MMMLMXXXVII'],
3988 => ['MMMLMXXXVIII'],
3989 => ['MMMLMXXXIX'],
3990 => ['MMMLMXL', 'MMMXM'],
3991 => ['MMMLMXLI', 'MMMXMI'],
3992 => ['MMMLMXLII', 'MMMXMII'],
3993 => ['MMMLMXLIII', 'MMMXMIII'],
3994 => ['MMMLMXLIV', 'MMMXMIV'],
3995 => ['MMMLMVL', 'MMMXMV', 'MMMVM'],
3996 => ['MMMLMVLI', 'MMMXMVI', 'MMMVMI'],
3997 => ['MMMLMVLII', 'MMMXMVII', 'MMMVMII'],
3998 => ['MMMLMVLIII', 'MMMXMVIII', 'MMMVMIII'],
3999 => ['MMMLMVLIV', 'MMMXMIX', 'MMMVMIV', 'MMMIM'],
];
private const THOUSANDS = ['', 'M', 'MM', 'MMM'];
private const HUNDREDS = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'];
private const TENS = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'];
private const ONES = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'];
const MAX_ROMAN_VALUE = 3999;
const MAX_ROMAN_STYLE = 4;
private static function valueOk(int $aValue, int $style): string
{
$origValue = $aValue;
$m = \intdiv($aValue, 1000);
$aValue %= 1000;
$c = \intdiv($aValue, 100);
$aValue %= 100;
$t = \intdiv($aValue, 10);
$aValue %= 10;
$result = self::THOUSANDS[$m] . self::HUNDREDS[$c] . self::TENS[$t] . self::ONES[$aValue];
if ($style > 0) {
if (array_key_exists($origValue, self::VALUES)) {
$arr = self::VALUES[$origValue];
$idx = min($style, count($arr)) - 1;
$result = $arr[$idx];
}
}
return $result;
}
private static function styleOk(int $aValue, int $style): string
{
return ($aValue < 0 || $aValue > self::MAX_ROMAN_VALUE) ? ExcelError::VALUE() : self::valueOk($aValue, $style);
}
public static function calculateRoman(int $aValue, int $style): string
{
return ($style < 0 || $style > self::MAX_ROMAN_STYLE) ? ExcelError::VALUE() : self::styleOk($aValue, $style);
}
/**
* ROMAN.
*
* Converts a number to Roman numeral
*
* @param mixed $aValue Number to convert
* Or can be an array of numbers
* @param mixed $style Number indicating one of five possible forms
* Or can be an array of styles
*
* @return array|string Roman numeral, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function evaluate(mixed $aValue, mixed $style = 0): array|string
{
if (is_array($aValue) || is_array($style)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $aValue, $style);
}
try {
$aValue = Helpers::validateNumericNullBool($aValue);
if (is_bool($style)) {
$style = $style ? 0 : 4;
}
$style = Helpers::validateNumericNullSubstitution($style, null);
} catch (Exception $e) {
return $e->getMessage();
}
return self::calculateRoman((int) $aValue, (int) $style);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Absolute.php 0000644 00000002012 15167673464 0021553 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Absolute
{
use ArrayEnabled;
/**
* ABS.
*
* Returns the result of builtin function abs after validating args.
*
* @param mixed $number Should be numeric, or can be an array of numbers
*
* @return array|float|int|string rounded number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function evaluate(mixed $number): array|string|int|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return abs($number);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php 0000644 00000013752 15167673464 0021364 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Ceiling
{
use ArrayEnabled;
/**
* CEILING.
*
* Returns number rounded up, away from zero, to the nearest multiple of significance.
* For example, if you want to avoid using pennies in your prices and your product is
* priced at $4.42, use the formula =CEILING(4.42,0.05) to round prices up to the
* nearest nickel.
*
* Excel Function:
* CEILING(number[,significance])
*
* @param array|float $number the number you want the ceiling
* Or can be an array of values
* @param array|float $significance the multiple to which you want to round
* Or can be an array of values
*
* @return array|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ceiling($number, $significance = null)
{
if (is_array($number) || is_array($significance)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance);
}
if ($significance === null) {
self::floorCheck1Arg();
}
try {
$number = Helpers::validateNumericNullBool($number);
$significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1);
} catch (Exception $e) {
return $e->getMessage();
}
return self::argumentsOk((float) $number, (float) $significance);
}
/**
* CEILING.MATH.
*
* Round a number down to the nearest integer or to the nearest multiple of significance.
*
* Excel Function:
* CEILING.MATH(number[,significance[,mode]])
*
* @param mixed $number Number to round
* Or can be an array of values
* @param mixed $significance Significance
* Or can be an array of values
* @param array|int $mode direction to round negative numbers
* Or can be an array of values
*
* @return array|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function math(mixed $number, mixed $significance = null, $mode = 0): array|string|float
{
if (is_array($number) || is_array($significance) || is_array($mode)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance, $mode);
}
try {
$number = Helpers::validateNumericNullBool($number);
$significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1);
$mode = Helpers::validateNumericNullSubstitution($mode, null);
} catch (Exception $e) {
return $e->getMessage();
}
if (empty($significance * $number)) {
return 0.0;
}
if (self::ceilingMathTest((float) $significance, (float) $number, (int) $mode)) {
return floor($number / $significance) * $significance;
}
return ceil($number / $significance) * $significance;
}
/**
* CEILING.PRECISE.
*
* Rounds number up, away from zero, to the nearest multiple of significance.
*
* Excel Function:
* CEILING.PRECISE(number[,significance])
*
* @param mixed $number the number you want to round
* Or can be an array of values
* @param array|float $significance the multiple to which you want to round
* Or can be an array of values
*
* @return array|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function precise(mixed $number, $significance = 1): array|string|float
{
if (is_array($number) || is_array($significance)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance);
}
try {
$number = Helpers::validateNumericNullBool($number);
$significance = Helpers::validateNumericNullSubstitution($significance, null);
} catch (Exception $e) {
return $e->getMessage();
}
if (!$significance) {
return 0.0;
}
$result = $number / abs($significance);
return ceil($result) * $significance * (($significance < 0) ? -1 : 1);
}
/**
* Let CEILINGMATH complexity pass Scrutinizer.
*/
private static function ceilingMathTest(float $significance, float $number, int $mode): bool
{
return ($significance < 0) || ($number < 0 && !empty($mode));
}
/**
* Avoid Scrutinizer problems concerning complexity.
*/
private static function argumentsOk(float $number, float $significance): float|string
{
if (empty($number * $significance)) {
return 0.0;
}
if (Helpers::returnSign($number) == Helpers::returnSign($significance)) {
return ceil($number / $significance) * $significance;
}
return ExcelError::NAN();
}
private static function floorCheck1Arg(): void
{
$compatibility = Functions::getCompatibilityMode();
if ($compatibility === Functions::COMPATIBILITY_EXCEL) {
throw new Exception('Excel requires 2 arguments for CEILING');
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php 0000644 00000007457 15167673464 0021723 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
class Factorial
{
use ArrayEnabled;
/**
* FACT.
*
* Returns the factorial of a number.
* The factorial of a number is equal to 1*2*3*...* number.
*
* Excel Function:
* FACT(factVal)
*
* @param array|float $factVal Factorial Value, or can be an array of numbers
*
* @return array|float|int|string Factorial, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function fact($factVal): array|string|float|int
{
if (is_array($factVal)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $factVal);
}
try {
$factVal = Helpers::validateNumericNullBool($factVal);
Helpers::validateNotNegative($factVal);
} catch (Exception $e) {
return $e->getMessage();
}
$factLoop = floor($factVal);
if ($factVal > $factLoop) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
return Statistical\Distributions\Gamma::gammaValue($factVal + 1);
}
}
$factorial = 1;
while ($factLoop > 1) {
$factorial *= $factLoop--;
}
return $factorial;
}
/**
* FACTDOUBLE.
*
* Returns the double factorial of a number.
*
* Excel Function:
* FACTDOUBLE(factVal)
*
* @param array|float $factVal Factorial Value, or can be an array of numbers
*
* @return array|float|int|string Double Factorial, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function factDouble($factVal): array|string|float|int
{
if (is_array($factVal)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $factVal);
}
try {
$factVal = Helpers::validateNumericNullSubstitution($factVal, 0);
Helpers::validateNotNegative($factVal);
} catch (Exception $e) {
return $e->getMessage();
}
$factLoop = floor($factVal);
$factorial = 1;
while ($factLoop > 1) {
$factorial *= $factLoop;
$factLoop -= 2;
}
return $factorial;
}
/**
* MULTINOMIAL.
*
* Returns the ratio of the factorial of a sum of values to the product of factorials.
*
* @param mixed[] $args An array of mixed values for the Data Series
*
* @return float|int|string The result, or a string containing an error
*/
public static function multinomial(...$args): string|int|float
{
$summer = 0;
$divisor = 1;
try {
// Loop through arguments
foreach (Functions::flattenArray($args) as $argx) {
$arg = Helpers::validateNumericNullSubstitution($argx, null);
Helpers::validateNotNegative($arg);
$arg = (int) $arg;
$summer += $arg;
$divisor *= self::fact($arg);
}
} catch (Exception $e) {
return $e->getMessage();
}
$summer = self::fact($summer);
return is_numeric($summer) ? ($summer / $divisor) : ExcelError::VALUE();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Random.php 0000644 00000006541 15167673464 0021230 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Random
{
use ArrayEnabled;
/**
* RAND.
*
* @return float|int Random number
*/
public static function rand(): int|float
{
return mt_rand(0, 10000000) / 10000000;
}
/**
* RANDBETWEEN.
*
* @param mixed $min Minimal value
* Or can be an array of values
* @param mixed $max Maximal value
* Or can be an array of values
*
* @return array|int|string Random number
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function randBetween(mixed $min, mixed $max): array|string|int
{
if (is_array($min) || is_array($max)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $min, $max);
}
try {
$min = (int) Helpers::validateNumericNullBool($min);
$max = (int) Helpers::validateNumericNullBool($max);
Helpers::validateNotNegative($max - $min);
} catch (Exception $e) {
return $e->getMessage();
}
return mt_rand($min, $max);
}
/**
* RANDARRAY.
*
* Generates a list of sequential numbers in an array.
*
* Excel Function:
* RANDARRAY([rows],[columns],[start],[step])
*
* @param mixed $rows the number of rows to return, defaults to 1
* @param mixed $columns the number of columns to return, defaults to 1
* @param mixed $min the minimum number to be returned, defaults to 0
* @param mixed $max the maximum number to be returned, defaults to 1
* @param bool $wholeNumber the type of numbers to return:
* False - Decimal numbers to 15 decimal places. (default)
* True - Whole (integer) numbers
*
* @return array|string The resulting array, or a string containing an error
*/
public static function randArray(mixed $rows = 1, mixed $columns = 1, mixed $min = 0, mixed $max = 1, bool $wholeNumber = false): string|array
{
try {
$rows = (int) Helpers::validateNumericNullSubstitution($rows, 1);
Helpers::validatePositive($rows);
$columns = (int) Helpers::validateNumericNullSubstitution($columns, 1);
Helpers::validatePositive($columns);
$min = Helpers::validateNumericNullSubstitution($min, 1);
$max = Helpers::validateNumericNullSubstitution($max, 1);
if ($max <= $min) {
return ExcelError::VALUE();
}
} catch (Exception $e) {
return $e->getMessage();
}
return array_chunk(
array_map(
function () use ($min, $max, $wholeNumber): int|float {
return $wholeNumber
? mt_rand((int) $min, (int) $max)
: (mt_rand() / mt_getrandmax()) * ($max - $min) + $min;
},
array_fill(0, $rows * $columns, $min)
),
max($columns, 1)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php 0000644 00000005063 15167673464 0021167 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Arabic
{
use ArrayEnabled;
private const ROMAN_LOOKUP = [
'M' => 1000,
'D' => 500,
'C' => 100,
'L' => 50,
'X' => 10,
'V' => 5,
'I' => 1,
];
/**
* Recursively calculate the arabic value of a roman numeral.
*/
private static function calculateArabic(array $roman, int &$sum = 0, int $subtract = 0): int
{
$numeral = array_shift($roman);
if (!isset(self::ROMAN_LOOKUP[$numeral])) {
throw new Exception('Invalid character detected');
}
$arabic = self::ROMAN_LOOKUP[$numeral];
if (count($roman) > 0 && isset(self::ROMAN_LOOKUP[$roman[0]]) && $arabic < self::ROMAN_LOOKUP[$roman[0]]) {
$subtract += $arabic;
} else {
$sum += ($arabic - $subtract);
$subtract = 0;
}
if (count($roman) > 0) {
self::calculateArabic($roman, $sum, $subtract);
}
return $sum;
}
/**
* ARABIC.
*
* Converts a Roman numeral to an Arabic numeral.
*
* Excel Function:
* ARABIC(text)
*
* @param string|string[] $roman Should be a string, or can be an array of strings
*
* @return array|int|string the arabic numberal contrived from the roman numeral
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function evaluate(mixed $roman): array|int|string
{
if (is_array($roman)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $roman);
}
// An empty string should return 0
$roman = substr(trim(strtoupper((string) $roman)), 0, 255);
if ($roman === '') {
return 0;
}
// Convert the roman numeral to an arabic number
$negativeNumber = $roman[0] === '-';
if ($negativeNumber) {
$roman = substr($roman, 1);
}
try {
$arabic = self::calculateArabic(str_split($roman));
} catch (Exception) {
return ExcelError::VALUE(); // Invalid character detected
}
if ($negativeNumber) {
$arabic *= -1; // The number should be negative
}
return $arabic;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php 0000644 00000003517 15167673464 0022454 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Helpers;
class Cosecant
{
use ArrayEnabled;
/**
* CSC.
*
* Returns the cosecant of an angle.
*
* @param array|float $angle Number, or can be an array of numbers
*
* @return array|float|string The cosecant of the angle
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function csc($angle)
{
if (is_array($angle)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle);
}
try {
$angle = Helpers::validateNumericNullBool($angle);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::verySmallDenominator(1.0, sin($angle));
}
/**
* CSCH.
*
* Returns the hyperbolic cosecant of an angle.
*
* @param array|float $angle Number, or can be an array of numbers
*
* @return array|float|string The hyperbolic cosecant of the angle
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function csch($angle)
{
if (is_array($angle)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle);
}
try {
$angle = Helpers::validateNumericNullBool($angle);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::verySmallDenominator(1.0, sinh($angle));
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php 0000644 00000012750 15167673464 0022314 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Helpers;
class Tangent
{
use ArrayEnabled;
/**
* TAN.
*
* Returns the result of builtin function tan after validating args.
*
* @param mixed $angle Should be numeric, or can be an array of numbers
*
* @return array|float|string tangent
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function tan(mixed $angle)
{
if (is_array($angle)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle);
}
try {
$angle = Helpers::validateNumericNullBool($angle);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::verySmallDenominator(sin($angle), cos($angle));
}
/**
* TANH.
*
* Returns the result of builtin function sinh after validating args.
*
* @param mixed $angle Should be numeric, or can be an array of numbers
*
* @return array|float|string hyperbolic tangent
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function tanh(mixed $angle): array|string|float
{
if (is_array($angle)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle);
}
try {
$angle = Helpers::validateNumericNullBool($angle);
} catch (Exception $e) {
return $e->getMessage();
}
return tanh($angle);
}
/**
* ATAN.
*
* Returns the arctangent of a number.
*
* @param array|float $number Number, or can be an array of numbers
*
* @return array|float|string The arctangent of the number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function atan($number)
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::numberOrNan(atan($number));
}
/**
* ATANH.
*
* Returns the inverse hyperbolic tangent of a number.
*
* @param array|float $number Number, or can be an array of numbers
*
* @return array|float|string The inverse hyperbolic tangent of the number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function atanh($number)
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::numberOrNan(atanh($number));
}
/**
* ATAN2.
*
* This function calculates the arc tangent of the two variables x and y. It is similar to
* calculating the arc tangent of y ÷ x, except that the signs of both arguments are used
* to determine the quadrant of the result.
* The arctangent is the angle from the x-axis to a line containing the origin (0, 0) and a
* point with coordinates (xCoordinate, yCoordinate). The angle is given in radians between
* -pi and pi, excluding -pi.
*
* Note that the Excel ATAN2() function accepts its arguments in the reverse order to the standard
* PHP atan2() function, so we need to reverse them here before calling the PHP atan() function.
*
* Excel Function:
* ATAN2(xCoordinate,yCoordinate)
*
* @param mixed $xCoordinate should be float, the x-coordinate of the point, or can be an array of numbers
* @param mixed $yCoordinate should be float, the y-coordinate of the point, or can be an array of numbers
*
* @return array|float|string The inverse tangent of the specified x- and y-coordinates, or a string containing an error
* If an array of numbers is passed as one of the arguments, then the returned result will also be an array
* with the same dimensions
*/
public static function atan2(mixed $xCoordinate, mixed $yCoordinate): array|string|float
{
if (is_array($xCoordinate) || is_array($yCoordinate)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $xCoordinate, $yCoordinate);
}
try {
$xCoordinate = Helpers::validateNumericNullBool($xCoordinate);
$yCoordinate = Helpers::validateNumericNullBool($yCoordinate);
} catch (Exception $e) {
return $e->getMessage();
}
if (($xCoordinate == 0) && ($yCoordinate == 0)) {
return ExcelError::DIV0();
}
return atan2($yCoordinate, $xCoordinate);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php 0000644 00000006606 15167673464 0021615 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Helpers;
class Sine
{
use ArrayEnabled;
/**
* SIN.
*
* Returns the result of builtin function sin after validating args.
*
* @param mixed $angle Should be numeric, or can be an array of numbers
*
* @return array|float|string sine
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function sin(mixed $angle): array|string|float
{
if (is_array($angle)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle);
}
try {
$angle = Helpers::validateNumericNullBool($angle);
} catch (Exception $e) {
return $e->getMessage();
}
return sin($angle);
}
/**
* SINH.
*
* Returns the result of builtin function sinh after validating args.
*
* @param mixed $angle Should be numeric, or can be an array of numbers
*
* @return array|float|string hyperbolic sine
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function sinh(mixed $angle): array|string|float
{
if (is_array($angle)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle);
}
try {
$angle = Helpers::validateNumericNullBool($angle);
} catch (Exception $e) {
return $e->getMessage();
}
return sinh($angle);
}
/**
* ASIN.
*
* Returns the arcsine of a number.
*
* @param array|float $number Number, or can be an array of numbers
*
* @return array|float|string The arcsine of the number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function asin($number)
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::numberOrNan(asin($number));
}
/**
* ASINH.
*
* Returns the inverse hyperbolic sine of a number.
*
* @param array|float $number Number, or can be an array of numbers
*
* @return array|float|string The inverse hyperbolic sine of the number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function asinh($number)
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::numberOrNan(asinh($number));
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php 0000644 00000006770 15167673464 0022643 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Helpers;
class Cotangent
{
use ArrayEnabled;
/**
* COT.
*
* Returns the cotangent of an angle.
*
* @param array|float $angle Number, or can be an array of numbers
*
* @return array|float|string The cotangent of the angle
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function cot($angle)
{
if (is_array($angle)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle);
}
try {
$angle = Helpers::validateNumericNullBool($angle);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::verySmallDenominator(cos($angle), sin($angle));
}
/**
* COTH.
*
* Returns the hyperbolic cotangent of an angle.
*
* @param array|float $angle Number, or can be an array of numbers
*
* @return array|float|string The hyperbolic cotangent of the angle
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function coth($angle)
{
if (is_array($angle)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle);
}
try {
$angle = Helpers::validateNumericNullBool($angle);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::verySmallDenominator(1.0, tanh($angle));
}
/**
* ACOT.
*
* Returns the arccotangent of a number.
*
* @param array|float $number Number, or can be an array of numbers
*
* @return array|float|string The arccotangent of the number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function acot($number): array|string|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return (M_PI / 2) - atan($number);
}
/**
* ACOTH.
*
* Returns the hyperbolic arccotangent of a number.
*
* @param array|float $number Number, or can be an array of numbers
*
* @return array|float|string The hyperbolic arccotangent of the number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function acoth($number)
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
$result = ($number === 1) ? NAN : (log(($number + 1) / ($number - 1)) / 2);
return Helpers::numberOrNan($result);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php 0000644 00000006672 15167673464 0022142 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Helpers;
class Cosine
{
use ArrayEnabled;
/**
* COS.
*
* Returns the result of builtin function cos after validating args.
*
* @param mixed $number Should be numeric, or can be an array of numbers
*
* @return array|float|string cosine
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function cos(mixed $number): array|string|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return cos($number);
}
/**
* COSH.
*
* Returns the result of builtin function cosh after validating args.
*
* @param mixed $number Should be numeric, or can be an array of numbers
*
* @return array|float|string hyperbolic cosine
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function cosh(mixed $number): array|string|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return cosh($number);
}
/**
* ACOS.
*
* Returns the arccosine of a number.
*
* @param array|float $number Number, or can be an array of numbers
*
* @return array|float|string The arccosine of the number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function acos($number)
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::numberOrNan(acos($number));
}
/**
* ACOSH.
*
* Returns the arc inverse hyperbolic cosine of a number.
*
* @param array|float $number Number, or can be an array of numbers
*
* @return array|float|string The inverse hyperbolic cosine of the number, or an error string
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function acosh($number)
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::numberOrNan(acosh($number));
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php 0000644 00000003505 15167673464 0022127 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Helpers;
class Secant
{
use ArrayEnabled;
/**
* SEC.
*
* Returns the secant of an angle.
*
* @param array|float $angle Number, or can be an array of numbers
*
* @return array|float|string The secant of the angle
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function sec($angle)
{
if (is_array($angle)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle);
}
try {
$angle = Helpers::validateNumericNullBool($angle);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::verySmallDenominator(1.0, cos($angle));
}
/**
* SECH.
*
* Returns the hyperbolic secant of an angle.
*
* @param array|float $angle Number, or can be an array of numbers
*
* @return array|float|string The hyperbolic secant of the angle
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function sech($angle)
{
if (is_array($angle)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle);
}
try {
$angle = Helpers::validateNumericNullBool($angle);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::verySmallDenominator(1.0, cosh($angle));
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php 0000644 00000011024 15167673464 0021575 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
class Subtotal
{
protected static function filterHiddenArgs(mixed $cellReference, mixed $args): array
{
return array_filter(
$args,
function ($index) use ($cellReference) {
$explodeArray = explode('.', $index);
$row = $explodeArray[1] ?? '';
if (!is_numeric($row)) {
return true;
}
return $cellReference->getWorksheet()->getRowDimension($row)->getVisible();
},
ARRAY_FILTER_USE_KEY
);
}
protected static function filterFormulaArgs(mixed $cellReference, mixed $args): array
{
return array_filter(
$args,
function ($index) use ($cellReference): bool {
$explodeArray = explode('.', $index);
$row = $explodeArray[1] ?? '';
$column = $explodeArray[2] ?? '';
$retVal = true;
if ($cellReference->getWorksheet()->cellExists($column . $row)) {
//take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula
$isFormula = $cellReference->getWorksheet()->getCell($column . $row)->isFormula();
$cellFormula = !preg_match(
'/^=.*\b(SUBTOTAL|AGGREGATE)\s*\(/i',
$cellReference->getWorksheet()->getCell($column . $row)->getValue() ?? ''
);
$retVal = !$isFormula || $cellFormula;
}
return $retVal;
},
ARRAY_FILTER_USE_KEY
);
}
/**
* @var array<int, callable>
*/
private const CALL_FUNCTIONS = [
1 => [Statistical\Averages::class, 'average'], // 1 and 101
[Statistical\Counts::class, 'COUNT'], // 2 and 102
[Statistical\Counts::class, 'COUNTA'], // 3 and 103
[Statistical\Maximum::class, 'max'], // 4 and 104
[Statistical\Minimum::class, 'min'], // 5 and 105
[Operations::class, 'product'], // 6 and 106
[Statistical\StandardDeviations::class, 'STDEV'], // 7 and 107
[Statistical\StandardDeviations::class, 'STDEVP'], // 8 and 108
[Sum::class, 'sumIgnoringStrings'], // 9 and 109
[Statistical\Variances::class, 'VAR'], // 10 and 110
[Statistical\Variances::class, 'VARP'], // 111 and 111
];
/**
* SUBTOTAL.
*
* Returns a subtotal in a list or database.
*
* @param mixed $functionType
* A number 1 to 11 that specifies which function to
* use in calculating subtotals within a range
* list
* Numbers 101 to 111 shadow the functions of 1 to 11
* but ignore any values in the range that are
* in hidden rows
* @param mixed[] $args A mixed data series of values
*/
public static function evaluate(mixed $functionType, ...$args): float|int|string
{
$cellReference = array_pop($args);
$bArgs = Functions::flattenArrayIndexed($args);
$aArgs = [];
// int keys must come before string keys for PHP 8.0+
// Otherwise, PHP thinks positional args follow keyword
// in the subsequent call to call_user_func_array.
// Fortunately, order of args is unimportant to Subtotal.
foreach ($bArgs as $key => $value) {
if (is_int($key)) {
$aArgs[$key] = $value;
}
}
foreach ($bArgs as $key => $value) {
if (!is_int($key)) {
$aArgs[$key] = $value;
}
}
try {
$subtotal = (int) Helpers::validateNumericNullBool($functionType);
} catch (Exception $e) {
return $e->getMessage();
}
// Calculate
if ($subtotal > 100) {
$aArgs = self::filterHiddenArgs($cellReference, $aArgs);
$subtotal -= 100;
}
$aArgs = self::filterFormulaArgs($cellReference, $aArgs);
if (array_key_exists($subtotal, self::CALL_FUNCTIONS)) {
$call = self::CALL_FUNCTIONS[$subtotal];
return call_user_func_array($call, $aArgs);
}
return ExcelError::VALUE();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php 0000644 00000006357 15167673464 0020561 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Sum
{
/**
* SUM, ignoring non-numeric non-error strings. This is eventually used by SUMIF.
*
* SUM computes the sum of all the values and cells referenced in the argument list.
*
* Excel Function:
* SUM(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function sumIgnoringStrings(mixed ...$args): float|int|string
{
$returnValue = 0;
// Loop through the arguments
foreach (Functions::flattenArray($args) as $arg) {
// Is it a numeric value?
if (is_numeric($arg)) {
$returnValue += $arg;
} elseif (ErrorValue::isError($arg)) {
return $arg;
}
}
return $returnValue;
}
/**
* SUM, returning error for non-numeric strings. This is used by Excel SUM function.
*
* SUM computes the sum of all the values and cells referenced in the argument list.
*
* Excel Function:
* SUM(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function sumErroringStrings(mixed ...$args): float|int|string|array
{
$returnValue = 0;
// Loop through the arguments
$aArgs = Functions::flattenArrayIndexed($args);
foreach ($aArgs as $k => $arg) {
// Is it a numeric value?
if (is_numeric($arg)) {
$returnValue += $arg;
} elseif (is_bool($arg)) {
$returnValue += (int) $arg;
} elseif (ErrorValue::isError($arg)) {
return $arg;
} elseif ($arg !== null && !Functions::isCellValue($k)) {
// ignore non-numerics from cell, but fail as literals (except null)
return ExcelError::VALUE();
}
}
return $returnValue;
}
/**
* SUMPRODUCT.
*
* Excel Function:
* SUMPRODUCT(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|int|string The result, or a string containing an error
*/
public static function product(mixed ...$args): string|int|float
{
$arrayList = $args;
$wrkArray = Functions::flattenArray(array_shift($arrayList));
$wrkCellCount = count($wrkArray);
for ($i = 0; $i < $wrkCellCount; ++$i) {
if ((!is_numeric($wrkArray[$i])) || (is_string($wrkArray[$i]))) {
$wrkArray[$i] = 0;
}
}
foreach ($arrayList as $matrixData) {
$array2 = Functions::flattenArray($matrixData);
$count = count($array2);
if ($wrkCellCount != $count) {
return ExcelError::VALUE();
}
foreach ($array2 as $i => $val) {
if ((!is_numeric($val)) || (is_string($val))) {
$val = 0;
}
$wrkArray[$i] *= $val;
}
}
return array_sum($wrkArray);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php 0000644 00000007555 15167673464 0022126 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class SumSquares
{
/**
* SUMSQ.
*
* SUMSQ returns the sum of the squares of the arguments
*
* Excel Function:
* SUMSQ(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function sumSquare(mixed ...$args): string|int|float
{
try {
$returnValue = 0;
// Loop through arguments
foreach (Functions::flattenArray($args) as $arg) {
$arg1 = Helpers::validateNumericNullSubstitution($arg, 0);
$returnValue += ($arg1 * $arg1);
}
} catch (Exception $e) {
return $e->getMessage();
}
return $returnValue;
}
private static function getCount(array $array1, array $array2): int
{
$count = count($array1);
if ($count !== count($array2)) {
throw new Exception(ExcelError::NA());
}
return $count;
}
/**
* These functions accept only numeric arguments, not even strings which are numeric.
*/
private static function numericNotString(mixed $item): bool
{
return is_numeric($item) && !is_string($item);
}
/**
* SUMX2MY2.
*
* @param mixed[] $matrixData1 Matrix #1
* @param mixed[] $matrixData2 Matrix #2
*/
public static function sumXSquaredMinusYSquared(array $matrixData1, array $matrixData2): string|int|float
{
try {
$array1 = Functions::flattenArray($matrixData1);
$array2 = Functions::flattenArray($matrixData2);
$count = self::getCount($array1, $array2);
$result = 0;
for ($i = 0; $i < $count; ++$i) {
if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) {
$result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]);
}
}
} catch (Exception $e) {
return $e->getMessage();
}
return $result;
}
/**
* SUMX2PY2.
*
* @param mixed[] $matrixData1 Matrix #1
* @param mixed[] $matrixData2 Matrix #2
*/
public static function sumXSquaredPlusYSquared(array $matrixData1, array $matrixData2): string|int|float
{
try {
$array1 = Functions::flattenArray($matrixData1);
$array2 = Functions::flattenArray($matrixData2);
$count = self::getCount($array1, $array2);
$result = 0;
for ($i = 0; $i < $count; ++$i) {
if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) {
$result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]);
}
}
} catch (Exception $e) {
return $e->getMessage();
}
return $result;
}
/**
* SUMXMY2.
*
* @param mixed[] $matrixData1 Matrix #1
* @param mixed[] $matrixData2 Matrix #2
*/
public static function sumXMinusYSquared(array $matrixData1, array $matrixData2): string|int|float
{
try {
$array1 = Functions::flattenArray($matrixData1);
$array2 = Functions::flattenArray($matrixData2);
$count = self::getCount($array1, $array2);
$result = 0;
for ($i = 0; $i < $count; ++$i) {
if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) {
$result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]);
}
}
} catch (Exception $e) {
return $e->getMessage();
}
return $result;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php 0000644 00000001775 15167673464 0020550 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Exp
{
use ArrayEnabled;
/**
* EXP.
*
* Returns the result of builtin function exp after validating args.
*
* @param mixed $number Should be numeric, or can be an array of numbers
*
* @return array|float|string Rounded number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function evaluate(mixed $number): array|string|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return exp($number);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Round.php 0000644 00000017216 15167673464 0021100 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Round
{
use ArrayEnabled;
private const ROUNDING_ADJUSTMENT = (PHP_VERSION_ID < 80400) ? 0 : 1e-14;
/**
* ROUND.
*
* Returns the result of builtin function round after validating args.
*
* @param mixed $number Should be numeric, or can be an array of numbers
* @param mixed $precision Should be int, or can be an array of numbers
*
* @return array|float|string Rounded number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function round(mixed $number, mixed $precision): array|string|float
{
if (is_array($number) || is_array($precision)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $precision);
}
try {
$number = Helpers::validateNumericNullBool($number);
$precision = Helpers::validateNumericNullBool($precision);
} catch (Exception $e) {
return $e->getMessage();
}
return round($number, (int) $precision);
}
/**
* ROUNDUP.
*
* Rounds a number up to a specified number of decimal places
*
* @param array|float $number Number to round, or can be an array of numbers
* @param array|int $digits Number of digits to which you want to round $number, or can be an array of numbers
*
* @return array|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function up($number, $digits): array|string|float
{
if (is_array($number) || is_array($digits)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $digits);
}
try {
$number = Helpers::validateNumericNullBool($number);
$digits = (int) Helpers::validateNumericNullSubstitution($digits, null);
} catch (Exception $e) {
return $e->getMessage();
}
if ($number == 0.0) {
return 0.0;
}
if ($number < 0.0) {
return round($number - 0.5 * 0.1 ** $digits + self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_DOWN);
}
return round($number + 0.5 * 0.1 ** $digits - self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_DOWN);
}
/**
* ROUNDDOWN.
*
* Rounds a number down to a specified number of decimal places
*
* @param array|float $number Number to round, or can be an array of numbers
* @param array|int $digits Number of digits to which you want to round $number, or can be an array of numbers
*
* @return array|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function down($number, $digits): array|string|float
{
if (is_array($number) || is_array($digits)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $digits);
}
try {
$number = Helpers::validateNumericNullBool($number);
$digits = (int) Helpers::validateNumericNullSubstitution($digits, null);
} catch (Exception $e) {
return $e->getMessage();
}
if ($number == 0.0) {
return 0.0;
}
if ($number < 0.0) {
return round($number + 0.5 * 0.1 ** $digits - self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_UP);
}
return round($number - 0.5 * 0.1 ** $digits + self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_UP);
}
/**
* MROUND.
*
* Rounds a number to the nearest multiple of a specified value
*
* @param mixed $number Expect float. Number to round, or can be an array of numbers
* @param mixed $multiple Expect int. Multiple to which you want to round, or can be an array of numbers.
*
* @return array|float|int|string Rounded Number, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function multiple(mixed $number, mixed $multiple): array|string|int|float
{
if (is_array($number) || is_array($multiple)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $multiple);
}
try {
$number = Helpers::validateNumericNullSubstitution($number, 0);
$multiple = Helpers::validateNumericNullSubstitution($multiple, null);
} catch (Exception $e) {
return $e->getMessage();
}
if ($number == 0 || $multiple == 0) {
return 0;
}
if ((Helpers::returnSign($number)) == (Helpers::returnSign($multiple))) {
$multiplier = 1 / $multiple;
return round($number * $multiplier) / $multiplier;
}
return ExcelError::NAN();
}
/**
* EVEN.
*
* Returns number rounded up to the nearest even integer.
* You can use this function for processing items that come in twos. For example,
* a packing crate accepts rows of one or two items. The crate is full when
* the number of items, rounded up to the nearest two, matches the crate's
* capacity.
*
* Excel Function:
* EVEN(number)
*
* @param array|float $number Number to round, or can be an array of numbers
*
* @return array|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function even($number): array|string|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::getEven($number);
}
/**
* ODD.
*
* Returns number rounded up to the nearest odd integer.
*
* @param array|float $number Number to round, or can be an array of numbers
*
* @return array|float|int|string Rounded Number, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function odd($number): array|string|int|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
$significance = Helpers::returnSign($number);
if ($significance == 0) {
return 1;
}
$result = ceil($number / $significance) * $significance;
if ($result == Helpers::getEven($result)) {
$result += $significance;
}
return $result;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php 0000644 00000002167 15167673464 0020710 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Sign
{
use ArrayEnabled;
/**
* SIGN.
*
* Determines the sign of a number. Returns 1 if the number is positive, zero (0)
* if the number is 0, and -1 if the number is negative.
*
* @param array|float $number Number to round, or can be an array of numbers
*
* @return array|int|string sign value, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function evaluate($number): array|string|int
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::returnSign($number);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php 0000644 00000005606 15167673464 0021413 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Helpers
{
/**
* Many functions accept null/false/true argument treated as 0/0/1.
*
* @return float|string quotient or DIV0 if denominator is too small
*/
public static function verySmallDenominator(float $numerator, float $denominator): string|float
{
return (abs($denominator) < 1.0E-12) ? ExcelError::DIV0() : ($numerator / $denominator);
}
/**
* Many functions accept null/false/true argument treated as 0/0/1.
*/
public static function validateNumericNullBool(mixed $number): int|float
{
$number = Functions::flattenSingleValue($number);
if ($number === null) {
return 0;
}
if (is_bool($number)) {
return (int) $number;
}
if (is_numeric($number)) {
return 0 + $number;
}
throw new Exception(ExcelError::throwError($number));
}
/**
* Validate numeric, but allow substitute for null.
*/
public static function validateNumericNullSubstitution(mixed $number, null|float|int $substitute): float|int
{
$number = Functions::flattenSingleValue($number);
if ($number === null && $substitute !== null) {
return $substitute;
}
if (is_numeric($number)) {
return 0 + $number;
}
throw new Exception(ExcelError::throwError($number));
}
/**
* Confirm number >= 0.
*/
public static function validateNotNegative(float|int $number, ?string $except = null): void
{
if ($number >= 0) {
return;
}
throw new Exception($except ?? ExcelError::NAN());
}
/**
* Confirm number > 0.
*/
public static function validatePositive(float|int $number, ?string $except = null): void
{
if ($number > 0) {
return;
}
throw new Exception($except ?? ExcelError::NAN());
}
/**
* Confirm number != 0.
*/
public static function validateNotZero(float|int $number): void
{
if ($number) {
return;
}
throw new Exception(ExcelError::DIV0());
}
public static function returnSign(float $number): int
{
return $number ? (($number > 0) ? 1 : -1) : 0;
}
public static function getEven(float $number): float
{
$significance = 2 * self::returnSign($number);
return $significance ? (ceil($number / $significance) * $significance) : 0;
}
/**
* Return NAN or value depending on argument.
*/
public static function numberOrNan(float $result): float|string
{
return is_nan($result) ? ExcelError::NAN() : $result;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php 0000644 00000002773 15167673464 0021106 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Trunc
{
use ArrayEnabled;
/**
* TRUNC.
*
* Truncates value to the number of fractional digits by number_digits.
*
* @param array|float $value Or can be an array of values
* @param array|int $digits Or can be an array of values
*
* @return array|float|string Truncated value, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function evaluate(array|float|string|null $value = 0, array|int|string $digits = 0): array|float|string
{
if (is_array($value) || is_array($digits)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $digits);
}
try {
$value = Helpers::validateNumericNullBool($value);
$digits = Helpers::validateNumericNullSubstitution($digits, null);
} catch (Exception $e) {
return $e->getMessage();
}
$digits = floor($digits);
// Truncate
$adjust = 10 ** $digits;
if (($digits > 0) && (rtrim((string) (int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) {
return $value;
}
return ((int) ($value * $adjust)) / $adjust;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Angle.php 0000644 00000003505 15167673464 0021033 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Angle
{
use ArrayEnabled;
/**
* DEGREES.
*
* Returns the result of builtin function rad2deg after validating args.
*
* @param mixed $number Should be numeric, or can be an array of numbers
*
* @return array|float|string Rounded number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toDegrees(mixed $number): array|string|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return rad2deg($number);
}
/**
* RADIANS.
*
* Returns the result of builtin function deg2rad after validating args.
*
* @param mixed $number Should be numeric, or can be an array of numbers
*
* @return array|float|string Rounded number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toRadians(mixed $number): array|string|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return deg2rad($number);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php 0000644 00000015052 15167673464 0021066 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Floor
{
use ArrayEnabled;
private static function floorCheck1Arg(): void
{
$compatibility = Functions::getCompatibilityMode();
if ($compatibility === Functions::COMPATIBILITY_EXCEL) {
throw new Exception('Excel requires 2 arguments for FLOOR');
}
}
/**
* FLOOR.
*
* Rounds number down, toward zero, to the nearest multiple of significance.
*
* Excel Function:
* FLOOR(number[,significance])
*
* @param mixed $number Expect float. Number to round
* Or can be an array of values
* @param mixed $significance Expect float. Significance
* Or can be an array of values
*
* @return array|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function floor(mixed $number, mixed $significance = null)
{
if (is_array($number) || is_array($significance)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance);
}
if ($significance === null) {
self::floorCheck1Arg();
}
try {
$number = Helpers::validateNumericNullBool($number);
$significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1);
} catch (Exception $e) {
return $e->getMessage();
}
return self::argumentsOk((float) $number, (float) $significance);
}
/**
* FLOOR.MATH.
*
* Round a number down to the nearest integer or to the nearest multiple of significance.
*
* Excel Function:
* FLOOR.MATH(number[,significance[,mode]])
*
* @param mixed $number Number to round
* Or can be an array of values
* @param mixed $significance Significance
* Or can be an array of values
* @param mixed $mode direction to round negative numbers
* Or can be an array of values
*
* @return array|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function math(mixed $number, mixed $significance = null, mixed $mode = 0)
{
if (is_array($number) || is_array($significance) || is_array($mode)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance, $mode);
}
try {
$number = Helpers::validateNumericNullBool($number);
$significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1);
$mode = Helpers::validateNumericNullSubstitution($mode, null);
} catch (Exception $e) {
return $e->getMessage();
}
return self::argsOk((float) $number, (float) $significance, (int) $mode);
}
/**
* FLOOR.PRECISE.
*
* Rounds number down, toward zero, to the nearest multiple of significance.
*
* Excel Function:
* FLOOR.PRECISE(number[,significance])
*
* @param array|float $number Number to round
* Or can be an array of values
* @param array|float $significance Significance
* Or can be an array of values
*
* @return array|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function precise($number, $significance = 1)
{
if (is_array($number) || is_array($significance)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance);
}
try {
$number = Helpers::validateNumericNullBool($number);
$significance = Helpers::validateNumericNullSubstitution($significance, null);
} catch (Exception $e) {
return $e->getMessage();
}
return self::argumentsOkPrecise((float) $number, (float) $significance);
}
/**
* Avoid Scrutinizer problems concerning complexity.
*/
private static function argumentsOkPrecise(float $number, float $significance): string|float
{
if ($significance == 0.0) {
return ExcelError::DIV0();
}
if ($number == 0.0) {
return 0.0;
}
return floor($number / abs($significance)) * abs($significance);
}
/**
* Avoid Scrutinizer complexity problems.
*
* @return float|string Rounded Number, or a string containing an error
*/
private static function argsOk(float $number, float $significance, int $mode): string|float
{
if (!$significance) {
return ExcelError::DIV0();
}
if (!$number) {
return 0.0;
}
if (self::floorMathTest($number, $significance, $mode)) {
return ceil($number / $significance) * $significance;
}
return floor($number / $significance) * $significance;
}
/**
* Let FLOORMATH complexity pass Scrutinizer.
*/
private static function floorMathTest(float $number, float $significance, int $mode): bool
{
return Helpers::returnSign($significance) == -1 || (Helpers::returnSign($number) == -1 && !empty($mode));
}
/**
* Avoid Scrutinizer problems concerning complexity.
*/
private static function argumentsOk(float $number, float $significance): string|float
{
if ($significance == 0.0) {
return ExcelError::DIV0();
}
if ($number == 0.0) {
return 0.0;
}
if (Helpers::returnSign($significance) == 1) {
return floor($number / $significance) * $significance;
}
if (Helpers::returnSign($number) == -1 && Helpers::returnSign($significance) == -1) {
return floor($number / $significance) * $significance;
}
return ExcelError::NAN();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php 0000644 00000007602 15167673464 0022434 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Combinations
{
use ArrayEnabled;
/**
* COMBIN.
*
* Returns the number of combinations for a given number of items. Use COMBIN to
* determine the total possible number of groups for a given number of items.
*
* Excel Function:
* COMBIN(numObjs,numInSet)
*
* @param mixed $numObjs Number of different objects, or can be an array of numbers
* @param mixed $numInSet Number of objects in each combination, or can be an array of numbers
*
* @return array|float|string Number of combinations, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function withoutRepetition(mixed $numObjs, mixed $numInSet): array|string|float
{
if (is_array($numObjs) || is_array($numInSet)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $numObjs, $numInSet);
}
try {
$numObjs = Helpers::validateNumericNullSubstitution($numObjs, null);
$numInSet = Helpers::validateNumericNullSubstitution($numInSet, null);
Helpers::validateNotNegative($numInSet);
Helpers::validateNotNegative($numObjs - $numInSet);
} catch (Exception $e) {
return $e->getMessage();
}
/** @var float */
$quotient = Factorial::fact($numObjs);
/** @var float */
$divisor1 = Factorial::fact($numObjs - $numInSet);
/** @var float */
$divisor2 = Factorial::fact($numInSet);
return round($quotient / ($divisor1 * $divisor2));
}
/**
* COMBINA.
*
* Returns the number of combinations for a given number of items. Use COMBIN to
* determine the total possible number of groups for a given number of items.
*
* Excel Function:
* COMBINA(numObjs,numInSet)
*
* @param mixed $numObjs Number of different objects, or can be an array of numbers
* @param mixed $numInSet Number of objects in each combination, or can be an array of numbers
*
* @return array|float|int|string Number of combinations, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function withRepetition(mixed $numObjs, mixed $numInSet): array|int|string|float
{
if (is_array($numObjs) || is_array($numInSet)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $numObjs, $numInSet);
}
try {
$numObjs = Helpers::validateNumericNullSubstitution($numObjs, null);
$numInSet = Helpers::validateNumericNullSubstitution($numInSet, null);
Helpers::validateNotNegative($numInSet);
Helpers::validateNotNegative($numObjs);
$numObjs = (int) $numObjs;
$numInSet = (int) $numInSet;
// Microsoft documentation says following is true, but Excel
// does not enforce this restriction.
//Helpers::validateNotNegative($numObjs - $numInSet);
if ($numObjs === 0) {
Helpers::validateNotNegative(-$numInSet);
return 1;
}
} catch (Exception $e) {
return $e->getMessage();
}
/** @var float */
$quotient = Factorial::fact($numObjs + $numInSet - 1);
/** @var float */
$divisor1 = Factorial::fact($numObjs - 1);
/** @var float */
$divisor2 = Factorial::fact($numInSet);
return round($quotient / ($divisor1 * $divisor2));
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php 0000644 00000006400 15167673464 0022113 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Logarithms
{
use ArrayEnabled;
/**
* LOG_BASE.
*
* Returns the logarithm of a number to a specified base. The default base is 10.
*
* Excel Function:
* LOG(number[,base])
*
* @param mixed $number The positive real number for which you want the logarithm
* Or can be an array of values
* @param mixed $base The base of the logarithm. If base is omitted, it is assumed to be 10.
* Or can be an array of values
*
* @return array|float|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function withBase(mixed $number, mixed $base = 10): array|string|float
{
if (is_array($number) || is_array($base)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $base);
}
try {
$number = Helpers::validateNumericNullBool($number);
Helpers::validatePositive($number);
$base = Helpers::validateNumericNullBool($base);
Helpers::validatePositive($base);
} catch (Exception $e) {
return $e->getMessage();
}
return log($number, $base);
}
/**
* LOG10.
*
* Returns the result of builtin function log after validating args.
*
* @param mixed $number Should be numeric
* Or can be an array of values
*
* @return array|float|string Rounded number
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function base10(mixed $number): array|string|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
Helpers::validatePositive($number);
} catch (Exception $e) {
return $e->getMessage();
}
return log10($number);
}
/**
* LN.
*
* Returns the result of builtin function log after validating args.
*
* @param mixed $number Should be numeric
* Or can be an array of values
*
* @return array|float|string Rounded number
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function natural(mixed $number): array|string|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
Helpers::validatePositive($number);
} catch (Exception $e) {
return $e->getMessage();
}
return log($number);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php 0000644 00000003565 15167673464 0020744 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Sqrt
{
use ArrayEnabled;
/**
* SQRT.
*
* Returns the result of builtin function sqrt after validating args.
*
* @param mixed $number Should be numeric, or can be an array of numbers
*
* @return array|float|string square root
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function sqrt(mixed $number)
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return Helpers::numberOrNan(sqrt($number));
}
/**
* SQRTPI.
*
* Returns the square root of (number * pi).
*
* @param array|float $number Number, or can be an array of numbers
*
* @return array|float|string Square Root of Number * Pi, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function pi($number): array|string|float
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullSubstitution($number, 0);
Helpers::validateNotNegative($number);
} catch (Exception $e) {
return $e->getMessage();
}
return sqrt($number * M_PI);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php 0000644 00000003664 15167673464 0020510 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Gcd
{
/**
* Recursively determine GCD.
*
* Returns the greatest common divisor of a series of numbers.
* The greatest common divisor is the largest integer that divides both
* number1 and number2 without a remainder.
*
* Excel Function:
* GCD(number1[,number2[, ...]])
*/
private static function evaluateGCD(float|int $a, float|int $b): float|int
{
return $b ? self::evaluateGCD($b, $a % $b) : $a;
}
/**
* GCD.
*
* Returns the greatest common divisor of a series of numbers.
* The greatest common divisor is the largest integer that divides both
* number1 and number2 without a remainder.
*
* Excel Function:
* GCD(number1[,number2[, ...]])
*
* @param mixed ...$args Data values
*
* @return float|int|string Greatest Common Divisor, or a string containing an error
*/
public static function evaluate(mixed ...$args)
{
try {
$arrayArgs = [];
foreach (Functions::flattenArray($args) as $value1) {
if ($value1 !== null) {
$value = Helpers::validateNumericNullSubstitution($value1, 1);
Helpers::validateNotNegative($value);
$arrayArgs[] = (int) $value;
}
}
} catch (Exception $e) {
return $e->getMessage();
}
if (count($arrayArgs) <= 0) {
return ExcelError::VALUE();
}
$gcd = (int) array_pop($arrayArgs);
do {
$gcd = self::evaluateGCD($gcd, (int) array_pop($arrayArgs));
} while (!empty($arrayArgs));
return $gcd;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php 0000644 00000002122 15167673464 0021517 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class IntClass
{
use ArrayEnabled;
/**
* INT.
*
* Casts a floating point value to an integer
*
* Excel Function:
* INT(number)
*
* @param array|float $number Number to cast to an integer, or can be an array of numbers
*
* @return array|int|string Integer value, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function evaluate($number): array|string|int
{
if (is_array($number)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
}
try {
$number = Helpers::validateNumericNullBool($number);
} catch (Exception $e) {
return $e->getMessage();
}
return (int) floor($number);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php 0000644 00000012421 15167673464 0023137 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use Matrix\Builder;
use Matrix\Div0Exception as MatrixDiv0Exception;
use Matrix\Exception as MatrixException;
use Matrix\Matrix;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class MatrixFunctions
{
/**
* Convert parameter to Matrix.
*
* @param mixed $matrixValues A matrix of values
*/
private static function getMatrix(mixed $matrixValues): Matrix
{
$matrixData = [];
if (!is_array($matrixValues)) {
$matrixValues = [[$matrixValues]];
}
$row = 0;
foreach ($matrixValues as $matrixRow) {
if (!is_array($matrixRow)) {
$matrixRow = [$matrixRow];
}
$column = 0;
foreach ($matrixRow as $matrixCell) {
if ((is_string($matrixCell)) || ($matrixCell === null)) {
throw new Exception(ExcelError::VALUE());
}
$matrixData[$row][$column] = $matrixCell;
++$column;
}
++$row;
}
return new Matrix($matrixData);
}
/**
* SEQUENCE.
*
* Generates a list of sequential numbers in an array.
*
* Excel Function:
* SEQUENCE(rows,[columns],[start],[step])
*
* @param mixed $rows the number of rows to return, defaults to 1
* @param mixed $columns the number of columns to return, defaults to 1
* @param mixed $start the first number in the sequence, defaults to 1
* @param mixed $step the amount to increment each subsequent value in the array, defaults to 1
*
* @return array|string The resulting array, or a string containing an error
*/
public static function sequence(mixed $rows = 1, mixed $columns = 1, mixed $start = 1, mixed $step = 1): string|array
{
try {
$rows = (int) Helpers::validateNumericNullSubstitution($rows, 1);
Helpers::validatePositive($rows);
$columns = (int) Helpers::validateNumericNullSubstitution($columns, 1);
Helpers::validatePositive($columns);
$start = Helpers::validateNumericNullSubstitution($start, 1);
$step = Helpers::validateNumericNullSubstitution($step, 1);
} catch (Exception $e) {
return $e->getMessage();
}
if ($step === 0) {
return array_chunk(
array_fill(0, $rows * $columns, $start),
max($columns, 1)
);
}
return array_chunk(
range($start, $start + (($rows * $columns - 1) * $step), $step),
max($columns, 1)
);
}
/**
* MDETERM.
*
* Returns the matrix determinant of an array.
*
* Excel Function:
* MDETERM(array)
*
* @param mixed $matrixValues A matrix of values
*
* @return float|string The result, or a string containing an error
*/
public static function determinant(mixed $matrixValues)
{
try {
$matrix = self::getMatrix($matrixValues);
return $matrix->determinant();
} catch (MatrixException) {
return ExcelError::VALUE();
} catch (Exception $e) {
return $e->getMessage();
}
}
/**
* MINVERSE.
*
* Returns the inverse matrix for the matrix stored in an array.
*
* Excel Function:
* MINVERSE(array)
*
* @param mixed $matrixValues A matrix of values
*
* @return array|string The result, or a string containing an error
*/
public static function inverse(mixed $matrixValues): array|string
{
try {
$matrix = self::getMatrix($matrixValues);
return $matrix->inverse()->toArray();
} catch (MatrixDiv0Exception) {
return ExcelError::NAN();
} catch (MatrixException) {
return ExcelError::VALUE();
} catch (Exception $e) {
return $e->getMessage();
}
}
/**
* MMULT.
*
* @param mixed $matrixData1 A matrix of values
* @param mixed $matrixData2 A matrix of values
*
* @return array|string The result, or a string containing an error
*/
public static function multiply(mixed $matrixData1, mixed $matrixData2): array|string
{
try {
$matrixA = self::getMatrix($matrixData1);
$matrixB = self::getMatrix($matrixData2);
return $matrixA->multiply($matrixB)->toArray();
} catch (MatrixException) {
return ExcelError::VALUE();
} catch (Exception $e) {
return $e->getMessage();
}
}
/**
* MUnit.
*
* @param mixed $dimension Number of rows and columns
*
* @return array|string The result, or a string containing an error
*/
public static function identity(mixed $dimension)
{
try {
$dimension = (int) Helpers::validateNumericNullBool($dimension);
Helpers::validatePositive($dimension, ExcelError::VALUE());
$matrix = Builder::createIdentityMatrix($dimension, 0)->toArray();
return $matrix;
} catch (Exception $e) {
return $e->getMessage();
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php 0000644 00000011627 15167673464 0022134 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Operations
{
use ArrayEnabled;
/**
* MOD.
*
* @param mixed $dividend Dividend
* Or can be an array of values
* @param mixed $divisor Divisor
* Or can be an array of values
*
* @return array|float|string Remainder, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function mod(mixed $dividend, mixed $divisor): array|string|float
{
if (is_array($dividend) || is_array($divisor)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dividend, $divisor);
}
try {
$dividend = Helpers::validateNumericNullBool($dividend);
$divisor = Helpers::validateNumericNullBool($divisor);
Helpers::validateNotZero($divisor);
} catch (Exception $e) {
return $e->getMessage();
}
if (($dividend < 0.0) && ($divisor > 0.0)) {
return $divisor - fmod(abs($dividend), $divisor);
}
if (($dividend > 0.0) && ($divisor < 0.0)) {
return $divisor + fmod($dividend, abs($divisor));
}
return fmod($dividend, $divisor);
}
/**
* POWER.
*
* Computes x raised to the power y.
*
* @param array|float|int|string $x Or can be an array of values
* @param array|float|int|string $y Or can be an array of values
*
* @return array|float|int|string The result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function power(array|float|int|string $x, array|float|int|string $y): array|float|int|string
{
if (is_array($x) || is_array($y)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $y);
}
try {
$x = Helpers::validateNumericNullBool($x);
$y = Helpers::validateNumericNullBool($y);
} catch (Exception $e) {
return $e->getMessage();
}
// Validate parameters
if (!$x && !$y) {
return ExcelError::NAN();
}
if (!$x && $y < 0.0) {
return ExcelError::DIV0();
}
// Return
$result = $x ** $y;
return Helpers::numberOrNan($result);
}
/**
* PRODUCT.
*
* PRODUCT returns the product of all the values and cells referenced in the argument list.
*
* Excel Function:
* PRODUCT(value1[,value2[, ...]])
*
* @param mixed ...$args Data values
*/
public static function product(mixed ...$args): string|float
{
$args = array_filter(
Functions::flattenArray($args),
fn ($value): bool => $value !== null
);
// Return value
$returnValue = (count($args) === 0) ? 0.0 : 1.0;
// Loop through arguments
foreach ($args as $arg) {
// Is it a numeric value?
if (is_numeric($arg)) {
$returnValue *= $arg;
} else {
return ExcelError::throwError($arg);
}
}
return (float) $returnValue;
}
/**
* QUOTIENT.
*
* QUOTIENT function returns the integer portion of a division. Numerator is the divided number
* and denominator is the divisor.
*
* Excel Function:
* QUOTIENT(value1,value2)
*
* @param mixed $numerator Expect float|int
* Or can be an array of values
* @param mixed $denominator Expect float|int
* Or can be an array of values
*
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function quotient(mixed $numerator, mixed $denominator): array|string|int
{
if (is_array($numerator) || is_array($denominator)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $numerator, $denominator);
}
try {
$numerator = Helpers::validateNumericNullSubstitution($numerator, 0);
$denominator = Helpers::validateNumericNullSubstitution($denominator, 0);
Helpers::validateNotZero($denominator);
} catch (Exception $e) {
return $e->getMessage();
}
return (int) ($numerator / $denominator);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php 0000644 00000007144 15167673464 0020523 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Lcm
{
//
// Private method to return an array of the factors of the input value
//
private static function factors(float $value): array
{
$startVal = floor(sqrt($value));
$factorArray = [];
for ($i = $startVal; $i > 1; --$i) {
if (($value % $i) == 0) {
$factorArray = array_merge($factorArray, self::factors($value / $i));
$factorArray = array_merge($factorArray, self::factors($i));
if ($i <= sqrt($value)) {
break;
}
}
}
if (!empty($factorArray)) {
rsort($factorArray);
return $factorArray;
}
return [(int) $value];
}
/**
* LCM.
*
* Returns the lowest common multiplier of a series of numbers
* The least common multiple is the smallest positive integer that is a multiple
* of all integer arguments number1, number2, and so on. Use LCM to add fractions
* with different denominators.
*
* Excel Function:
* LCM(number1[,number2[, ...]])
*
* @param mixed ...$args Data values
*
* @return int|string Lowest Common Multiplier, or a string containing an error
*/
public static function evaluate(mixed ...$args): int|string
{
try {
$arrayArgs = [];
$anyZeros = 0;
$anyNonNulls = 0;
foreach (Functions::flattenArray($args) as $value1) {
$anyNonNulls += (int) ($value1 !== null);
$value = Helpers::validateNumericNullSubstitution($value1, 1);
Helpers::validateNotNegative($value);
$arrayArgs[] = (int) $value;
$anyZeros += (int) !((bool) $value);
}
self::testNonNulls($anyNonNulls);
if ($anyZeros) {
return 0;
}
} catch (Exception $e) {
return $e->getMessage();
}
$returnValue = 1;
$allPoweredFactors = [];
// Loop through arguments
foreach ($arrayArgs as $value) {
$myFactors = self::factors(floor($value));
$myCountedFactors = array_count_values($myFactors);
$myPoweredFactors = [];
foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) {
$myPoweredFactors[$myCountedFactor] = $myCountedFactor ** $myCountedPower;
}
self::processPoweredFactors($allPoweredFactors, $myPoweredFactors);
}
foreach ($allPoweredFactors as $allPoweredFactor) {
$returnValue *= (int) $allPoweredFactor;
}
return $returnValue;
}
private static function processPoweredFactors(array &$allPoweredFactors, array &$myPoweredFactors): void
{
foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) {
if (isset($allPoweredFactors[$myPoweredValue])) {
if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) {
$allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
}
} else {
$allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
}
}
}
private static function testNonNulls(int $anyNonNulls): void
{
if (!$anyNonNulls) {
throw new Exception(ExcelError::VALUE());
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ExcelError.php 0000644 00000006160 15167673465 0022626 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Information;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
class ExcelError
{
use ArrayEnabled;
/**
* List of error codes.
*
* @var array<string, string>
*/
public const ERROR_CODES = [
'null' => '#NULL!', // 1
'divisionbyzero' => '#DIV/0!', // 2
'value' => '#VALUE!', // 3
'reference' => '#REF!', // 4
'name' => '#NAME?', // 5
'num' => '#NUM!', // 6
'na' => '#N/A', // 7
'gettingdata' => '#GETTING_DATA', // 8
'spill' => '#SPILL!', // 9
'connect' => '#CONNECT!', //10
'blocked' => '#BLOCKED!', //11
'unknown' => '#UNKNOWN!', //12
'field' => '#FIELD!', //13
'calculation' => '#CALC!', //14
];
public static function throwError(mixed $value): string
{
return in_array($value, self::ERROR_CODES, true) ? $value : self::ERROR_CODES['value'];
}
/**
* ERROR_TYPE.
*
* @param mixed $value Value to check
*/
public static function type(mixed $value = ''): array|int|string
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
$i = 1;
foreach (self::ERROR_CODES as $errorCode) {
if ($value === $errorCode) {
return $i;
}
++$i;
}
return self::NA();
}
/**
* NULL.
*
* Returns the error value #NULL!
*
* @return string #NULL!
*/
public static function null(): string
{
return self::ERROR_CODES['null'];
}
/**
* NaN.
*
* Returns the error value #NUM!
*
* @return string #NUM!
*/
public static function NAN(): string
{
return self::ERROR_CODES['num'];
}
/**
* REF.
*
* Returns the error value #REF!
*
* @return string #REF!
*/
public static function REF(): string
{
return self::ERROR_CODES['reference'];
}
/**
* NA.
*
* Excel Function:
* =NA()
*
* Returns the error value #N/A
* #N/A is the error value that means "no value is available."
*
* @return string #N/A!
*/
public static function NA(): string
{
return self::ERROR_CODES['na'];
}
/**
* VALUE.
*
* Returns the error value #VALUE!
*
* @return string #VALUE!
*/
public static function VALUE(): string
{
return self::ERROR_CODES['value'];
}
/**
* NAME.
*
* Returns the error value #NAME?
*
* @return string #NAME?
*/
public static function NAME(): string
{
return self::ERROR_CODES['name'];
}
/**
* DIV0.
*
* @return string #DIV/0!
*/
public static function DIV0(): string
{
return self::ERROR_CODES['divisionbyzero'];
}
/**
* CALC.
*
* @return string #CALC!
*/
public static function CALC(): string
{
return self::ERROR_CODES['calculation'];
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php 0000644 00000003665 15167673465 0022651 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Information;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
class ErrorValue
{
use ArrayEnabled;
/**
* IS_ERR.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isErr(mixed $value = ''): array|bool
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return self::isError($value) && (!self::isNa(($value)));
}
/**
* IS_ERROR.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isError(mixed $value = ''): array|bool
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if (!is_string($value)) {
return false;
}
return in_array($value, ExcelError::ERROR_CODES, true);
}
/**
* IS_NA.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isNa(mixed $value = ''): array|bool
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return $value === ExcelError::NA();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/Value.php 0000644 00000023605 15167673465 0021633 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Information;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Value
{
use ArrayEnabled;
/**
* IS_BLANK.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isBlank(mixed $value = null): array|bool
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return $value === null;
}
/**
* IS_REF.
*
* @param mixed $value Value to check
*/
public static function isRef(mixed $value, ?Cell $cell = null): bool
{
if ($cell === null || $value === $cell->getCoordinate()) {
return false;
}
$cellValue = Functions::trimTrailingRange($value);
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/ui', $cellValue) === 1) {
[$worksheet, $cellValue] = Worksheet::extractSheetTitle($cellValue, true);
if (!empty($worksheet) && $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheet) === null) {
return false;
}
[$column, $row] = Coordinate::indexesFromString($cellValue ?? '');
if ($column > 16384 || $row > 1048576) {
return false;
}
return true;
}
$namedRange = $cell->getWorksheet()->getParentOrThrow()->getNamedRange($value);
return $namedRange instanceof NamedRange;
}
/**
* IS_EVEN.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isEven(mixed $value = null): array|string|bool
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if ($value === null) {
return ExcelError::NAME();
} elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
return ExcelError::VALUE();
}
return ((int) fmod($value, 2)) === 0;
}
/**
* IS_ODD.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isOdd(mixed $value = null): array|string|bool
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if ($value === null) {
return ExcelError::NAME();
} elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
return ExcelError::VALUE();
}
return ((int) fmod($value, 2)) !== 0;
}
/**
* IS_NUMBER.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isNumber(mixed $value = null): array|bool
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if (is_string($value)) {
return false;
}
return is_numeric($value);
}
/**
* IS_LOGICAL.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isLogical(mixed $value = null): array|bool
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return is_bool($value);
}
/**
* IS_TEXT.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isText(mixed $value = null): array|bool
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return is_string($value) && !ErrorValue::isError($value);
}
/**
* IS_NONTEXT.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isNonText(mixed $value = null): array|bool
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return !self::isText($value);
}
/**
* ISFORMULA.
*
* @param mixed $cellReference The cell to check
* @param ?Cell $cell The current cell (containing this formula)
*/
public static function isFormula(mixed $cellReference = '', ?Cell $cell = null): array|bool|string
{
if ($cell === null) {
return ExcelError::REF();
}
$fullCellReference = Functions::expandDefinedName((string) $cellReference, $cell);
if (str_contains($cellReference, '!')) {
$cellReference = Functions::trimSheetFromCellReference($cellReference);
$cellReferences = Coordinate::extractAllCellReferencesInRange($cellReference);
if (count($cellReferences) > 1) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $cellReferences, $cell);
}
}
$fullCellReference = Functions::trimTrailingRange($fullCellReference);
preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $fullCellReference, $matches);
$fullCellReference = $matches[6] . $matches[7];
$worksheetName = str_replace("''", "'", trim($matches[2], "'"));
$worksheet = (!empty($worksheetName))
? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheetName)
: $cell->getWorksheet();
return ($worksheet !== null) ? $worksheet->getCell($fullCellReference)->isFormula() : ExcelError::REF();
}
/**
* N.
*
* Returns a value converted to a number
*
* @param null|mixed $value The value you want converted
*
* @return number|string N converts values listed in the following table
* If value is or refers to N returns
* A number That number value
* A date The Excel serialized number of that date
* TRUE 1
* FALSE 0
* An error value The error value
* Anything else 0
*/
public static function asNumber($value = null)
{
while (is_array($value)) {
$value = array_shift($value);
}
switch (gettype($value)) {
case 'double':
case 'float':
case 'integer':
return $value;
case 'boolean':
return (int) $value;
case 'string':
// Errors
if (($value !== '') && ($value[0] == '#')) {
return $value;
}
break;
}
return 0;
}
/**
* TYPE.
*
* Returns a number that identifies the type of a value
*
* @param null|mixed $value The value you want tested
*
* @return int N converts values listed in the following table
* If value is or refers to N returns
* A number 1
* Text 2
* Logical Value 4
* An error value 16
* Array or Matrix 64
*/
public static function type($value = null): int
{
$value = Functions::flattenArrayIndexed($value);
if (is_array($value) && (count($value) > 1)) {
end($value);
$a = key($value);
// Range of cells is an error
if (Functions::isCellValue($a)) {
return 16;
// Test for Matrix
} elseif (Functions::isMatrixValue($a)) {
return 64;
}
} elseif (empty($value)) {
// Empty Cell
return 1;
}
$value = Functions::flattenSingleValue($value);
if (($value === null) || (is_float($value)) || (is_int($value))) {
return 1;
} elseif (is_bool($value)) {
return 4;
} elseif (is_array($value)) {
return 64;
} elseif (is_string($value)) {
// Errors
if (($value !== '') && ($value[0] == '#')) {
return 16;
}
return 2;
}
return 0;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php 0000644 00000001610 15167673465 0024274 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class LookupRefValidations
{
public static function validateInt(mixed $value): int
{
if (!is_numeric($value)) {
if (ErrorValue::isError($value)) {
throw new Exception($value);
}
throw new Exception(ExcelError::VALUE());
}
return (int) floor((float) $value);
}
public static function validatePositiveInt(mixed $value, bool $allowZero = true): int
{
$value = self::validateInt($value);
if (($allowZero === false && $value <= 0) || $value < 0) {
throw new Exception(ExcelError::VALUE());
}
return $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php 0000644 00000002555 15167673465 0022146 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
class Hyperlink
{
/**
* HYPERLINK.
*
* Excel Function:
* =HYPERLINK(linkURL, [displayName])
*
* @param mixed $linkURL Expect string. Value to check, is also the value returned when no error
* @param mixed $displayName Expect string. Value to return when testValue is an error condition
* @param ?Cell $cell The cell to set the hyperlink in
*
* @return string The value of $displayName (or $linkURL if $displayName was blank)
*/
public static function set(mixed $linkURL = '', mixed $displayName = null, ?Cell $cell = null): string
{
$linkURL = ($linkURL === null) ? '' : Functions::flattenSingleValue($linkURL);
$displayName = ($displayName === null) ? '' : Functions::flattenSingleValue($displayName);
if ((!is_object($cell)) || (trim($linkURL) == '')) {
return ExcelError::REF();
}
if ((is_object($displayName)) || trim($displayName) == '') {
$displayName = $linkURL;
}
$cell->getHyperlink()->setUrl($linkURL);
$cell->getHyperlink()->setTooltip($displayName);
return $displayName;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Selection.php 0000644 00000002766 15167673465 0022132 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Selection
{
use ArrayEnabled;
/**
* CHOOSE.
*
* Uses lookup_value to return a value from the list of value arguments.
* Use CHOOSE to select one of up to 254 values based on the lookup_value.
*
* Excel Function:
* =CHOOSE(index_num, value1, [value2], ...)
*
* @param mixed $chosenEntry The entry to select from the list (indexed from 1)
* @param mixed ...$chooseArgs Data values
*
* @return mixed The selected value
*/
public static function choose(mixed $chosenEntry, mixed ...$chooseArgs): mixed
{
if (is_array($chosenEntry)) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $chosenEntry, ...$chooseArgs);
}
$entryCount = count($chooseArgs) - 1;
if (is_numeric($chosenEntry)) {
--$chosenEntry;
} else {
return ExcelError::VALUE();
}
$chosenEntry = floor($chosenEntry);
if (($chosenEntry < 0) || ($chosenEntry > $entryCount)) {
return ExcelError::VALUE();
}
if (is_array($chooseArgs[$chosenEntry])) {
return Functions::flattenArray($chooseArgs[$chosenEntry]);
}
return $chooseArgs[$chosenEntry];
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php 0000644 00000004312 15167673465 0022236 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
abstract class LookupBase
{
protected static function validateLookupArray(mixed $lookup_array): void
{
if (!is_array($lookup_array)) {
throw new Exception(ExcelError::REF());
}
}
/** @param float|int|string $index_number */
protected static function validateIndexLookup(array $lookup_array, $index_number): int
{
// index_number must be a number greater than or equal to 1.
// Excel results are inconsistent when index is non-numeric.
// VLOOKUP(whatever, whatever, SQRT(-1)) yields NUM error, but
// VLOOKUP(whatever, whatever, cellref) yields REF error
// when cellref is '=SQRT(-1)'. So just try our best here.
// Similar results if string (literal yields VALUE, cellRef REF).
if (!is_numeric($index_number)) {
throw new Exception(ExcelError::throwError($index_number));
}
if ($index_number < 1) {
throw new Exception(ExcelError::VALUE());
}
// index_number must be less than or equal to the number of columns in lookup_array
if (empty($lookup_array)) {
throw new Exception(ExcelError::REF());
}
return (int) $index_number;
}
protected static function checkMatch(
bool $bothNumeric,
bool $bothNotNumeric,
bool $notExactMatch,
int $rowKey,
string $cellDataLower,
string $lookupLower,
?int $rowNumber
): ?int {
// remember the last key, but only if datatypes match
if ($bothNumeric || $bothNotNumeric) {
// Spreadsheets software returns first exact match,
// we have sorted and we might have broken key orders
// we want the first one (by its initial index)
if ($notExactMatch) {
$rowNumber = $rowKey;
} elseif (($cellDataLower == $lookupLower) && (($rowNumber === null) || ($rowKey < $rowNumber))) {
$rowNumber = $rowKey;
}
}
return $rowNumber;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php 0000644 00000010415 15167673465 0021437 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Matrix
{
use ArrayEnabled;
/**
* Helper function; NOT an implementation of any Excel Function.
*/
public static function isColumnVector(array $values): bool
{
return count($values, COUNT_RECURSIVE) === (count($values, COUNT_NORMAL) * 2);
}
/**
* Helper function; NOT an implementation of any Excel Function.
*/
public static function isRowVector(array $values): bool
{
return count($values, COUNT_RECURSIVE) > 1
&& (count($values, COUNT_NORMAL) === 1 || count($values, COUNT_RECURSIVE) === count($values, COUNT_NORMAL));
}
/**
* TRANSPOSE.
*
* @param array|mixed $matrixData A matrix of values
*/
public static function transpose($matrixData): array
{
$returnMatrix = [];
if (!is_array($matrixData)) {
$matrixData = [[$matrixData]];
}
$column = 0;
foreach ($matrixData as $matrixRow) {
$row = 0;
foreach ($matrixRow as $matrixCell) {
$returnMatrix[$row][$column] = $matrixCell;
++$row;
}
++$column;
}
return $returnMatrix;
}
/**
* INDEX.
*
* Uses an index to choose a value from a reference or array
*
* Excel Function:
* =INDEX(range_array, row_num, [column_num], [area_num])
*
* @param mixed $matrix A range of cells or an array constant
* @param mixed $rowNum The row in the array or range from which to return a value.
* If row_num is omitted, column_num is required.
* Or can be an array of values
* @param mixed $columnNum The column in the array or range from which to return a value.
* If column_num is omitted, row_num is required.
* Or can be an array of values
*
* TODO Provide support for area_num, currently not supported
*
* @return mixed the value of a specified cell or array of cells
* If an array of values is passed as the $rowNum and/or $columnNum arguments, then the returned result
* will also be an array with the same dimensions
*/
public static function index(mixed $matrix, mixed $rowNum = 0, mixed $columnNum = null): mixed
{
if (is_array($rowNum) || is_array($columnNum)) {
return self::evaluateArrayArgumentsSubsetFrom([self::class, __FUNCTION__], 1, $matrix, $rowNum, $columnNum);
}
$rowNum = $rowNum ?? 0;
$originalColumnNum = $columnNum;
$columnNum = $columnNum ?? 0;
try {
$rowNum = LookupRefValidations::validatePositiveInt($rowNum);
$columnNum = LookupRefValidations::validatePositiveInt($columnNum);
} catch (Exception $e) {
return $e->getMessage();
}
if (!is_array($matrix) || ($rowNum > count($matrix))) {
return ExcelError::REF();
}
$rowKeys = array_keys($matrix);
$columnKeys = @array_keys($matrix[$rowKeys[0]]);
if ($columnNum > count($columnKeys)) {
return ExcelError::REF();
}
if ($originalColumnNum === null && 1 < count($columnKeys)) {
return ExcelError::REF();
}
if ($columnNum === 0) {
return self::extractRowValue($matrix, $rowKeys, $rowNum);
}
$columnNum = $columnKeys[--$columnNum];
if ($rowNum === 0) {
return array_map(
fn ($value): array => [$value],
array_column($matrix, $columnNum)
);
}
$rowNum = $rowKeys[--$rowNum];
return $matrix[$rowNum][$columnNum];
}
private static function extractRowValue(array $matrix, array $rowKeys, int $rowNum): mixed
{
if ($rowNum === 0) {
return $matrix;
}
$rowNum = $rowKeys[--$rowNum];
$row = $matrix[$rowNum];
if (is_array($row)) {
return [$rowNum => $row];
}
return $row;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Address.php 0000644 00000011326 15167673465 0021562 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
class Address
{
use ArrayEnabled;
public const ADDRESS_ABSOLUTE = 1;
public const ADDRESS_COLUMN_RELATIVE = 2;
public const ADDRESS_ROW_RELATIVE = 3;
public const ADDRESS_RELATIVE = 4;
public const REFERENCE_STYLE_A1 = true;
public const REFERENCE_STYLE_R1C1 = false;
/**
* ADDRESS.
*
* Creates a cell address as text, given specified row and column numbers.
*
* Excel Function:
* =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText])
*
* @param mixed $row Row number (integer) to use in the cell reference
* Or can be an array of values
* @param mixed $column Column number (integer) to use in the cell reference
* Or can be an array of values
* @param mixed $relativity Integer flag indicating the type of reference to return
* 1 or omitted Absolute
* 2 Absolute row; relative column
* 3 Relative row; absolute column
* 4 Relative
* Or can be an array of values
* @param mixed $referenceStyle A logical (boolean) value that specifies the A1 or R1C1 reference style.
* TRUE or omitted ADDRESS returns an A1-style reference
* FALSE ADDRESS returns an R1C1-style reference
* Or can be an array of values
* @param mixed $sheetName Optional Name of worksheet to use
* Or can be an array of values
*
* @return array|string If an array of values is passed as the $testValue argument, then the returned result will also be
* an array with the same dimensions
*/
public static function cell(mixed $row, mixed $column, mixed $relativity = 1, mixed $referenceStyle = true, mixed $sheetName = ''): array|string
{
if (
is_array($row) || is_array($column)
|| is_array($relativity) || is_array($referenceStyle) || is_array($sheetName)
) {
return self::evaluateArrayArguments(
[self::class, __FUNCTION__],
$row,
$column,
$relativity,
$referenceStyle,
$sheetName
);
}
$relativity = $relativity ?? 1;
$referenceStyle = $referenceStyle ?? true;
if (($row < 1) || ($column < 1)) {
return ExcelError::VALUE();
}
$sheetName = self::sheetName($sheetName);
if (is_int($referenceStyle)) {
$referenceStyle = (bool) $referenceStyle;
}
if ((!is_bool($referenceStyle)) || $referenceStyle === self::REFERENCE_STYLE_A1) {
return self::formatAsA1($row, $column, $relativity, $sheetName);
}
return self::formatAsR1C1($row, $column, $relativity, $sheetName);
}
private static function sheetName(string $sheetName): string
{
if ($sheetName > '') {
if (str_contains($sheetName, ' ') || str_contains($sheetName, '[')) {
$sheetName = "'{$sheetName}'";
}
$sheetName .= '!';
}
return $sheetName;
}
private static function formatAsA1(int $row, int $column, int $relativity, string $sheetName): string
{
$rowRelative = $columnRelative = '$';
if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) {
$columnRelative = '';
}
if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) {
$rowRelative = '';
}
$column = Coordinate::stringFromColumnIndex($column);
return "{$sheetName}{$columnRelative}{$column}{$rowRelative}{$row}";
}
private static function formatAsR1C1(int $row, int $column, int $relativity, string $sheetName): string
{
if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) {
$column = "[{$column}]";
}
if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) {
$row = "[{$row}]";
}
[$rowChar, $colChar] = AddressHelper::getRowAndColumnChars();
return "{$sheetName}$rowChar{$row}$colChar{$column}";
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php 0000644 00000016275 15167673465 0024340 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class RowColumnInformation
{
/**
* Test if cellAddress is null or whitespace string.
*
* @param null|array|string $cellAddress A reference to a range of cells
*/
private static function cellAddressNullOrWhitespace($cellAddress): bool
{
return $cellAddress === null || (!is_array($cellAddress) && trim($cellAddress) === '');
}
private static function cellColumn(?Cell $cell): int
{
return ($cell !== null) ? Coordinate::columnIndexFromString($cell->getColumn()) : 1;
}
/**
* COLUMN.
*
* Returns the column number of the given cell reference
* If the cell reference is a range of cells, COLUMN returns the column numbers of each column
* in the reference as a horizontal array.
* If cell reference is omitted, and the function is being called through the calculation engine,
* then it is assumed to be the reference of the cell in which the COLUMN function appears;
* otherwise this function returns 1.
*
* Excel Function:
* =COLUMN([cellAddress])
*
* @param null|array|string $cellAddress A reference to a range of cells for which you want the column numbers
*
* @return int|int[]
*/
public static function COLUMN($cellAddress = null, ?Cell $cell = null): int|array
{
if (self::cellAddressNullOrWhitespace($cellAddress)) {
return self::cellColumn($cell);
}
if (is_array($cellAddress)) {
foreach ($cellAddress as $columnKey => $value) {
$columnKey = (string) preg_replace('/[^a-z]/i', '', $columnKey);
return Coordinate::columnIndexFromString($columnKey);
}
return self::cellColumn($cell);
}
$cellAddress = $cellAddress ?? '';
if ($cell != null) {
[,, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell);
[,, $cellAddress] = Helpers::extractCellAddresses($cellAddress, true, $cell->getWorksheet(), $sheetName);
}
[, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
$cellAddress ??= '';
if (str_contains($cellAddress, ':')) {
[$startAddress, $endAddress] = explode(':', $cellAddress);
$startAddress = (string) preg_replace('/[^a-z]/i', '', $startAddress);
$endAddress = (string) preg_replace('/[^a-z]/i', '', $endAddress);
return range(
Coordinate::columnIndexFromString($startAddress),
Coordinate::columnIndexFromString($endAddress)
);
}
$cellAddress = (string) preg_replace('/[^a-z]/i', '', $cellAddress);
return Coordinate::columnIndexFromString($cellAddress);
}
/**
* COLUMNS.
*
* Returns the number of columns in an array or reference.
*
* Excel Function:
* =COLUMNS(cellAddress)
*
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells
* for which you want the number of columns
*
* @return int|string The number of columns in cellAddress, or a string if arguments are invalid
*/
public static function COLUMNS($cellAddress = null)
{
if (self::cellAddressNullOrWhitespace($cellAddress)) {
return 1;
}
if (!is_array($cellAddress)) {
return ExcelError::VALUE();
}
reset($cellAddress);
$isMatrix = (is_numeric(key($cellAddress)));
[$columns, $rows] = Calculation::getMatrixDimensions($cellAddress);
if ($isMatrix) {
return $rows;
}
return $columns;
}
private static function cellRow(?Cell $cell): int
{
return ($cell !== null) ? $cell->getRow() : 1;
}
/**
* ROW.
*
* Returns the row number of the given cell reference
* If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference
* as a vertical array.
* If cell reference is omitted, and the function is being called through the calculation engine,
* then it is assumed to be the reference of the cell in which the ROW function appears;
* otherwise this function returns 1.
*
* Excel Function:
* =ROW([cellAddress])
*
* @param null|array|string $cellAddress A reference to a range of cells for which you want the row numbers
*
* @return int|mixed[]
*/
public static function ROW($cellAddress = null, ?Cell $cell = null): int|array
{
if (self::cellAddressNullOrWhitespace($cellAddress)) {
return self::cellRow($cell);
}
if (is_array($cellAddress)) {
foreach ($cellAddress as $rowKey => $rowValue) {
foreach ($rowValue as $columnKey => $cellValue) {
return (int) preg_replace('/\D/', '', $rowKey);
}
}
return self::cellRow($cell);
}
$cellAddress = $cellAddress ?? '';
if ($cell !== null) {
[,, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell);
[,, $cellAddress] = Helpers::extractCellAddresses($cellAddress, true, $cell->getWorksheet(), $sheetName);
}
[, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
$cellAddress ??= '';
if (str_contains($cellAddress, ':')) {
[$startAddress, $endAddress] = explode(':', $cellAddress);
$startAddress = (int) (string) preg_replace('/\D/', '', $startAddress);
$endAddress = (int) (string) preg_replace('/\D/', '', $endAddress);
return array_map(
fn ($value): array => [$value],
range($startAddress, $endAddress)
);
}
[$cellAddress] = explode(':', $cellAddress);
return (int) preg_replace('/\D/', '', $cellAddress);
}
/**
* ROWS.
*
* Returns the number of rows in an array or reference.
*
* Excel Function:
* =ROWS(cellAddress)
*
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells
* for which you want the number of rows
*
* @return int|string The number of rows in cellAddress, or a string if arguments are invalid
*/
public static function ROWS($cellAddress = null)
{
if (self::cellAddressNullOrWhitespace($cellAddress)) {
return 1;
}
if (!is_array($cellAddress)) {
return ExcelError::VALUE();
}
reset($cellAddress);
$isMatrix = (is_numeric(key($cellAddress)));
[$columns, $rows] = Calculation::getMatrixDimensions($cellAddress);
if ($isMatrix) {
return $columns;
}
return $rows;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Sort.php 0000644 00000026172 15167673465 0021131 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Sort extends LookupRefValidations
{
public const ORDER_ASCENDING = 1;
public const ORDER_DESCENDING = -1;
/**
* SORT
* The SORT function returns a sorted array of the elements in an array.
* The returned array is the same shape as the provided array argument.
* Both $sortIndex and $sortOrder can be arrays, to provide multi-level sorting.
*
* @param mixed $sortArray The range of cells being sorted
* @param mixed $sortIndex The column or row number within the sortArray to sort on
* @param mixed $sortOrder Flag indicating whether to sort ascending or descending
* Ascending = 1 (self::ORDER_ASCENDING)
* Descending = -1 (self::ORDER_DESCENDING)
* @param mixed $byColumn Whether the sort should be determined by row (the default) or by column
*
* @return mixed The sorted values from the sort range
*/
public static function sort(mixed $sortArray, mixed $sortIndex = 1, mixed $sortOrder = self::ORDER_ASCENDING, mixed $byColumn = false): mixed
{
if (!is_array($sortArray)) {
// Scalars are always returned "as is"
return $sortArray;
}
$sortArray = self::enumerateArrayKeys($sortArray);
$byColumn = (bool) $byColumn;
$lookupIndexSize = $byColumn ? count($sortArray) : count($sortArray[0]);
try {
// If $sortIndex and $sortOrder are scalars, then convert them into arrays
if (is_scalar($sortIndex)) {
$sortIndex = [$sortIndex];
$sortOrder = is_scalar($sortOrder) ? [$sortOrder] : $sortOrder;
}
// but the values of those array arguments still need validation
$sortOrder = (empty($sortOrder) ? [self::ORDER_ASCENDING] : $sortOrder);
self::validateArrayArgumentsForSort($sortIndex, $sortOrder, $lookupIndexSize);
} catch (Exception $e) {
return $e->getMessage();
}
// We want a simple, enumrated array of arrays where we can reference column by its index number.
$sortArray = array_values(array_map('array_values', $sortArray));
return ($byColumn === true)
? self::sortByColumn($sortArray, $sortIndex, $sortOrder)
: self::sortByRow($sortArray, $sortIndex, $sortOrder);
}
/**
* SORTBY
* The SORTBY function sorts the contents of a range or array based on the values in a corresponding range or array.
* The returned array is the same shape as the provided array argument.
* Both $sortIndex and $sortOrder can be arrays, to provide multi-level sorting.
*
* @param mixed $sortArray The range of cells being sorted
* @param mixed $args
* At least one additional argument must be provided, The vector or range to sort on
* After that, arguments are passed as pairs:
* sort order: ascending or descending
* Ascending = 1 (self::ORDER_ASCENDING)
* Descending = -1 (self::ORDER_DESCENDING)
* additional arrays or ranges for multi-level sorting
*
* @return mixed The sorted values from the sort range
*/
public static function sortBy(mixed $sortArray, mixed ...$args): mixed
{
if (!is_array($sortArray)) {
// Scalars are always returned "as is"
return $sortArray;
}
$sortArray = self::enumerateArrayKeys($sortArray);
$lookupArraySize = count($sortArray);
$argumentCount = count($args);
try {
$sortBy = $sortOrder = [];
for ($i = 0; $i < $argumentCount; $i += 2) {
$sortBy[] = self::validateSortVector($args[$i], $lookupArraySize);
$sortOrder[] = self::validateSortOrder($args[$i + 1] ?? self::ORDER_ASCENDING);
}
} catch (Exception $e) {
return $e->getMessage();
}
return self::processSortBy($sortArray, $sortBy, $sortOrder);
}
private static function enumerateArrayKeys(array $sortArray): array
{
array_walk(
$sortArray,
function (&$columns): void {
if (is_array($columns)) {
$columns = array_values($columns);
}
}
);
return array_values($sortArray);
}
private static function validateScalarArgumentsForSort(mixed &$sortIndex, mixed &$sortOrder, int $sortArraySize): void
{
if (is_array($sortIndex) || is_array($sortOrder)) {
throw new Exception(ExcelError::VALUE());
}
$sortIndex = self::validatePositiveInt($sortIndex, false);
if ($sortIndex > $sortArraySize) {
throw new Exception(ExcelError::VALUE());
}
$sortOrder = self::validateSortOrder($sortOrder);
}
private static function validateSortVector(mixed $sortVector, int $sortArraySize): array
{
if (!is_array($sortVector)) {
throw new Exception(ExcelError::VALUE());
}
// It doesn't matter if it's a row or a column vectors, it works either way
$sortVector = Functions::flattenArray($sortVector);
if (count($sortVector) !== $sortArraySize) {
throw new Exception(ExcelError::VALUE());
}
return $sortVector;
}
private static function validateSortOrder(mixed $sortOrder): int
{
$sortOrder = self::validateInt($sortOrder);
if (($sortOrder == self::ORDER_ASCENDING || $sortOrder === self::ORDER_DESCENDING) === false) {
throw new Exception(ExcelError::VALUE());
}
return $sortOrder;
}
private static function validateArrayArgumentsForSort(array &$sortIndex, mixed &$sortOrder, int $sortArraySize): void
{
// It doesn't matter if they're row or column vectors, it works either way
$sortIndex = Functions::flattenArray($sortIndex);
$sortOrder = Functions::flattenArray($sortOrder);
if (
count($sortOrder) === 0 || count($sortOrder) > $sortArraySize
|| (count($sortOrder) > count($sortIndex))
) {
throw new Exception(ExcelError::VALUE());
}
if (count($sortIndex) > count($sortOrder)) {
// If $sortOrder has fewer elements than $sortIndex, then the last order element is repeated.
$sortOrder = array_merge(
$sortOrder,
array_fill(0, count($sortIndex) - count($sortOrder), array_pop($sortOrder))
);
}
foreach ($sortIndex as $key => &$value) {
self::validateScalarArgumentsForSort($value, $sortOrder[$key], $sortArraySize);
}
}
private static function prepareSortVectorValues(array $sortVector): array
{
// Strings should be sorted case-insensitive; with booleans converted to locale-strings
return array_map(
function ($value) {
if (is_bool($value)) {
return ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
} elseif (is_string($value)) {
return StringHelper::strToLower($value);
}
return $value;
},
$sortVector
);
}
/**
* @param array[] $sortIndex
* @param int[] $sortOrder
*/
private static function processSortBy(array $sortArray, array $sortIndex, array $sortOrder): array
{
$sortArguments = [];
$sortData = [];
foreach ($sortIndex as $index => $sortValues) {
$sortData[] = $sortValues;
$sortArguments[] = self::prepareSortVectorValues($sortValues);
$sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC;
}
$sortVector = self::executeVectorSortQuery($sortData, $sortArguments);
return self::sortLookupArrayFromVector($sortArray, $sortVector);
}
/**
* @param int[] $sortIndex
* @param int[] $sortOrder
*/
private static function sortByRow(array $sortArray, array $sortIndex, array $sortOrder): array
{
$sortVector = self::buildVectorForSort($sortArray, $sortIndex, $sortOrder);
return self::sortLookupArrayFromVector($sortArray, $sortVector);
}
/**
* @param int[] $sortIndex
* @param int[] $sortOrder
*/
private static function sortByColumn(array $sortArray, array $sortIndex, array $sortOrder): array
{
$sortArray = Matrix::transpose($sortArray);
$result = self::sortByRow($sortArray, $sortIndex, $sortOrder);
return Matrix::transpose($result);
}
/**
* @param int[] $sortIndex
* @param int[] $sortOrder
*/
private static function buildVectorForSort(array $sortArray, array $sortIndex, array $sortOrder): array
{
$sortArguments = [];
$sortData = [];
foreach ($sortIndex as $index => $sortIndexValue) {
$sortValues = array_column($sortArray, $sortIndexValue - 1);
$sortData[] = $sortValues;
$sortArguments[] = self::prepareSortVectorValues($sortValues);
$sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC;
}
$sortData = self::executeVectorSortQuery($sortData, $sortArguments);
return $sortData;
}
private static function executeVectorSortQuery(array $sortData, array $sortArguments): array
{
$sortData = Matrix::transpose($sortData);
// We need to set an index that can be retained, as array_multisort doesn't maintain numeric keys.
$sortDataIndexed = [];
foreach ($sortData as $key => $value) {
$sortDataIndexed[Coordinate::stringFromColumnIndex($key + 1)] = $value;
}
unset($sortData);
$sortArguments[] = &$sortDataIndexed;
array_multisort(...$sortArguments);
// After the sort, we restore the numeric keys that will now be in the correct, sorted order
$sortedData = [];
foreach (array_keys($sortDataIndexed) as $key) {
$sortedData[] = Coordinate::columnIndexFromString($key) - 1;
}
return $sortedData;
}
private static function sortLookupArrayFromVector(array $sortArray, array $sortVector): array
{
// Building a new array in the correct (sorted) order works; but may be memory heavy for larger arrays
$sortedArray = [];
foreach ($sortVector as $index) {
$sortedArray[] = $sortArray[$index];
}
return $sortedArray;
// uksort(
// $lookupArray,
// function (int $a, int $b) use (array $sortVector) {
// return $sortVector[$a] <=> $sortVector[$b];
// }
// );
//
// return $lookupArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php 0000644 00000005357 15167673465 0021606 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\DefinedName;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Helpers
{
public const CELLADDRESS_USE_A1 = true;
public const CELLADDRESS_USE_R1C1 = false;
private static function convertR1C1(string &$cellAddress1, ?string &$cellAddress2, bool $a1, ?int $baseRow = null, ?int $baseCol = null): string
{
if ($a1 === self::CELLADDRESS_USE_R1C1) {
$cellAddress1 = AddressHelper::convertToA1($cellAddress1, $baseRow ?? 1, $baseCol ?? 1);
if ($cellAddress2) {
$cellAddress2 = AddressHelper::convertToA1($cellAddress2, $baseRow ?? 1, $baseCol ?? 1);
}
}
return $cellAddress1 . ($cellAddress2 ? ":$cellAddress2" : '');
}
private static function adjustSheetTitle(string &$sheetTitle, ?string $value): void
{
if ($sheetTitle) {
$sheetTitle .= '!';
if (stripos($value ?? '', $sheetTitle) === 0) {
$sheetTitle = '';
}
}
}
public static function extractCellAddresses(string $cellAddress, bool $a1, Worksheet $sheet, string $sheetName = '', ?int $baseRow = null, ?int $baseCol = null): array
{
$cellAddress1 = $cellAddress;
$cellAddress2 = null;
$namedRange = DefinedName::resolveName($cellAddress1, $sheet, $sheetName);
if ($namedRange !== null) {
$workSheet = $namedRange->getWorkSheet();
$sheetTitle = ($workSheet === null) ? '' : $workSheet->getTitle();
$value = (string) preg_replace('/^=/', '', $namedRange->getValue());
self::adjustSheetTitle($sheetTitle, $value);
$cellAddress1 = $sheetTitle . $value;
$cellAddress = $cellAddress1;
$a1 = self::CELLADDRESS_USE_A1;
}
if (str_contains($cellAddress, ':')) {
[$cellAddress1, $cellAddress2] = explode(':', $cellAddress);
}
$cellAddress = self::convertR1C1($cellAddress1, $cellAddress2, $a1, $baseRow, $baseCol);
return [$cellAddress1, $cellAddress2, $cellAddress];
}
public static function extractWorksheet(string $cellAddress, Cell $cell): array
{
$sheetName = '';
if (str_contains($cellAddress, '!')) {
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
$sheetName = trim($sheetName, "'");
}
$worksheet = ($sheetName !== '')
? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($sheetName)
: $cell->getWorksheet();
return [$cellAddress, $worksheet, $sheetName];
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php 0000644 00000023024 15167673465 0022210 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class ExcelMatch
{
use ArrayEnabled;
public const MATCHTYPE_SMALLEST_VALUE = -1;
public const MATCHTYPE_FIRST_VALUE = 0;
public const MATCHTYPE_LARGEST_VALUE = 1;
/**
* MATCH.
*
* The MATCH function searches for a specified item in a range of cells
*
* Excel Function:
* =MATCH(lookup_value, lookup_array, [match_type])
*
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param mixed $lookupArray The range of cells being searched
* @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below.
* If match_type is 1 or -1, the list has to be ordered.
*
* @return array|float|int|string The relative position of the found item
*/
public static function MATCH(mixed $lookupValue, mixed $lookupArray, mixed $matchType = self::MATCHTYPE_LARGEST_VALUE): array|string|int|float
{
if (is_array($lookupValue)) {
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $matchType);
}
$lookupArray = Functions::flattenArray($lookupArray);
try {
// Input validation
self::validateLookupValue($lookupValue);
$matchType = self::validateMatchType($matchType);
self::validateLookupArray($lookupArray);
$keySet = array_keys($lookupArray);
if ($matchType == self::MATCHTYPE_LARGEST_VALUE) {
// If match_type is 1 the list has to be processed from last to first
$lookupArray = array_reverse($lookupArray);
$keySet = array_reverse($keySet);
}
$lookupArray = self::prepareLookupArray($lookupArray, $matchType);
} catch (Exception $e) {
return $e->getMessage();
}
// MATCH() is not case sensitive, so we convert lookup value to be lower cased if it's a string type.
if (is_string($lookupValue)) {
$lookupValue = StringHelper::strToLower($lookupValue);
}
$valueKey = match ($matchType) {
self::MATCHTYPE_LARGEST_VALUE => self::matchLargestValue($lookupArray, $lookupValue, $keySet),
self::MATCHTYPE_FIRST_VALUE => self::matchFirstValue($lookupArray, $lookupValue),
default => self::matchSmallestValue($lookupArray, $lookupValue),
};
if ($valueKey !== null) {
return ++$valueKey;
}
// Unsuccessful in finding a match, return #N/A error value
return ExcelError::NA();
}
private static function matchFirstValue(array $lookupArray, mixed $lookupValue): int|string|null
{
if (is_string($lookupValue)) {
$valueIsString = true;
$wildcard = WildcardMatch::wildcard($lookupValue);
} else {
$valueIsString = false;
$wildcard = '';
}
$valueIsNumeric = is_int($lookupValue) || is_float($lookupValue);
foreach ($lookupArray as $i => $lookupArrayValue) {
if (
$valueIsString
&& is_string($lookupArrayValue)
) {
if (WildcardMatch::compare($lookupArrayValue, $wildcard)) {
return $i; // wildcard match
}
} else {
if ($lookupArrayValue === $lookupValue) {
return $i; // exact match
}
if (
$valueIsNumeric
&& (is_float($lookupArrayValue) || is_int($lookupArrayValue))
&& $lookupArrayValue == $lookupValue
) {
return $i; // exact match
}
}
}
return null;
}
private static function matchLargestValue(array $lookupArray, mixed $lookupValue, array $keySet): mixed
{
if (is_string($lookupValue)) {
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
$wildcard = WildcardMatch::wildcard($lookupValue);
foreach (array_reverse($lookupArray) as $i => $lookupArrayValue) {
if (is_string($lookupArrayValue) && WildcardMatch::compare($lookupArrayValue, $wildcard)) {
return $i;
}
}
} else {
foreach ($lookupArray as $i => $lookupArrayValue) {
if ($lookupArrayValue === $lookupValue) {
return $keySet[$i];
}
}
}
}
$valueIsNumeric = is_int($lookupValue) || is_float($lookupValue);
foreach ($lookupArray as $i => $lookupArrayValue) {
if ($valueIsNumeric && (is_int($lookupArrayValue) || is_float($lookupArrayValue))) {
if ($lookupArrayValue <= $lookupValue) {
return array_search($i, $keySet);
}
}
$typeMatch = gettype($lookupValue) === gettype($lookupArrayValue);
if ($typeMatch && ($lookupArrayValue <= $lookupValue)) {
return array_search($i, $keySet);
}
}
return null;
}
private static function matchSmallestValue(array $lookupArray, mixed $lookupValue): int|string|null
{
$valueKey = null;
if (is_string($lookupValue)) {
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
$wildcard = WildcardMatch::wildcard($lookupValue);
foreach ($lookupArray as $i => $lookupArrayValue) {
if (is_string($lookupArrayValue) && WildcardMatch::compare($lookupArrayValue, $wildcard)) {
return $i;
}
}
}
}
$valueIsNumeric = is_int($lookupValue) || is_float($lookupValue);
// The basic algorithm is:
// Iterate and keep the highest match until the next element is smaller than the searched value.
// Return immediately if perfect match is found
foreach ($lookupArray as $i => $lookupArrayValue) {
$typeMatch = gettype($lookupValue) === gettype($lookupArrayValue);
$bothNumeric = $valueIsNumeric && (is_int($lookupArrayValue) || is_float($lookupArrayValue));
if ($lookupArrayValue === $lookupValue) {
// Another "special" case. If a perfect match is found,
// the algorithm gives up immediately
return $i;
}
if ($bothNumeric && $lookupValue == $lookupArrayValue) {
return $i; // exact match, as above
}
if (($typeMatch || $bothNumeric) && $lookupArrayValue >= $lookupValue) {
$valueKey = $i;
} elseif ($typeMatch && $lookupArrayValue < $lookupValue) {
//Excel algorithm gives up immediately if the first element is smaller than the searched value
break;
}
}
return $valueKey;
}
private static function validateLookupValue(mixed $lookupValue): void
{
// Lookup_value type has to be number, text, or logical values
if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) {
throw new Exception(ExcelError::NA());
}
}
private static function validateMatchType(mixed $matchType): int
{
// Match_type is 0, 1 or -1
// However Excel accepts other numeric values,
// including numeric strings and floats.
// It seems to just be interested in the sign.
if (!is_numeric($matchType)) {
throw new Exception(ExcelError::Value());
}
if ($matchType > 0) {
return self::MATCHTYPE_LARGEST_VALUE;
}
if ($matchType < 0) {
return self::MATCHTYPE_SMALLEST_VALUE;
}
return self::MATCHTYPE_FIRST_VALUE;
}
private static function validateLookupArray(array $lookupArray): void
{
// Lookup_array should not be empty
$lookupArraySize = count($lookupArray);
if ($lookupArraySize <= 0) {
throw new Exception(ExcelError::NA());
}
}
private static function prepareLookupArray(array $lookupArray, mixed $matchType): array
{
// Lookup_array should contain only number, text, or logical values, or empty (null) cells
foreach ($lookupArray as $i => $value) {
// check the type of the value
if ((!is_numeric($value)) && (!is_string($value)) && (!is_bool($value)) && ($value !== null)) {
throw new Exception(ExcelError::NA());
}
// Convert strings to lowercase for case-insensitive testing
if (is_string($value)) {
$lookupArray[$i] = StringHelper::strToLower($value);
}
if (
($value === null)
&& (($matchType == self::MATCHTYPE_LARGEST_VALUE) || ($matchType == self::MATCHTYPE_SMALLEST_VALUE))
) {
unset($lookupArray[$i]);
}
}
return $lookupArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php 0000644 00000011724 15167673465 0021740 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Indirect
{
/**
* Determine whether cell address is in A1 (true) or R1C1 (false) format.
*
* @param mixed $a1fmt Expect bool Helpers::CELLADDRESS_USE_A1 or CELLADDRESS_USE_R1C1,
* but can be provided as numeric which is cast to bool
*/
private static function a1Format(mixed $a1fmt): bool
{
$a1fmt = Functions::flattenSingleValue($a1fmt);
if ($a1fmt === null) {
return Helpers::CELLADDRESS_USE_A1;
}
if (is_string($a1fmt)) {
throw new Exception(ExcelError::VALUE());
}
return (bool) $a1fmt;
}
/**
* Convert cellAddress to string, verify not null string.
*/
private static function validateAddress(array|string|null $cellAddress): string
{
$cellAddress = Functions::flattenSingleValue($cellAddress);
if (!is_string($cellAddress) || !$cellAddress) {
throw new Exception(ExcelError::REF());
}
return $cellAddress;
}
/**
* INDIRECT.
*
* Returns the reference specified by a text string.
* References are immediately evaluated to display their contents.
*
* Excel Function:
* =INDIRECT(cellAddress, bool) where the bool argument is optional
*
* @param array|string $cellAddress $cellAddress The cell address of the current cell (containing this formula)
* @param mixed $a1fmt Expect bool Helpers::CELLADDRESS_USE_A1 or CELLADDRESS_USE_R1C1,
* but can be provided as numeric which is cast to bool
* @param Cell $cell The current cell (containing this formula)
*
* @return array|string An array containing a cell or range of cells, or a string on error
*/
public static function INDIRECT($cellAddress, mixed $a1fmt, Cell $cell): string|array
{
[$baseCol, $baseRow] = Coordinate::indexesFromString($cell->getCoordinate());
try {
$a1 = self::a1Format($a1fmt);
$cellAddress = self::validateAddress($cellAddress);
} catch (Exception $e) {
return $e->getMessage();
}
[$cellAddress, $worksheet, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell);
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) {
$cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress));
} elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) {
$cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress));
}
try {
[$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName, $baseRow, $baseCol);
} catch (Exception) {
return ExcelError::REF();
}
if (
(!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress1, $matches))
|| (($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress2, $matches)))
) {
return ExcelError::REF();
}
return self::extractRequiredCells($worksheet, $cellAddress);
}
/**
* Extract range values.
*
* @return array Array of values in range if range contains more than one element.
* Otherwise, a single value is returned.
*/
private static function extractRequiredCells(?Worksheet $worksheet, string $cellAddress): array
{
return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null)
->extractCellRange($cellAddress, $worksheet, false);
}
private static function handleRowColumnRanges(?Worksheet $worksheet, string $start, string $end): string
{
// Being lazy, we're only checking a single row/column to get the max
if (ctype_digit($start) && $start <= 1048576) {
// Max 16,384 columns for Excel2007
$endColRef = ($worksheet !== null) ? $worksheet->getHighestDataColumn((int) $start) : AddressRange::MAX_COLUMN;
return "A{$start}:{$endColRef}{$end}";
} elseif (ctype_alpha($start) && strlen($start) <= 3) {
// Max 1,048,576 rows for Excel2007
$endRowRef = ($worksheet !== null) ? $worksheet->getHighestDataRow($start) : AddressRange::MAX_ROW;
return "{$start}1:{$end}{$endRowRef}";
}
return "{$start}:{$end}";
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php 0000644 00000006670 15167673465 0021454 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Lookup
{
use ArrayEnabled;
/**
* LOOKUP
* The LOOKUP function searches for value either from a one-row or one-column range or from an array.
*
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param mixed $lookupVector The range of cells being searched
* @param null|mixed $resultVector The column from which the matching value must be returned
*
* @return mixed The value of the found cell
*/
public static function lookup(mixed $lookupValue, mixed $lookupVector, $resultVector = null): mixed
{
if (is_array($lookupValue)) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $lookupValue, $lookupVector, $resultVector);
}
if (!is_array($lookupVector)) {
return ExcelError::NA();
}
$hasResultVector = isset($resultVector);
$lookupRows = self::rowCount($lookupVector);
$lookupColumns = self::columnCount($lookupVector);
// we correctly orient our results
if (($lookupRows === 1 && $lookupColumns > 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) {
$lookupVector = Matrix::transpose($lookupVector);
$lookupRows = self::rowCount($lookupVector);
$lookupColumns = self::columnCount($lookupVector);
}
$resultVector = self::verifyResultVector($resultVector ?? $lookupVector);
if ($lookupRows === 2 && !$hasResultVector) {
$resultVector = array_pop($lookupVector);
$lookupVector = array_shift($lookupVector);
}
if ($lookupColumns !== 2) {
$lookupVector = self::verifyLookupValues($lookupVector, $resultVector);
}
return VLookup::lookup($lookupValue, $lookupVector, 2);
}
private static function verifyLookupValues(array $lookupVector, array $resultVector): array
{
foreach ($lookupVector as &$value) {
if (is_array($value)) {
$k = array_keys($value);
$key1 = $key2 = array_shift($k);
++$key2;
$dataValue1 = $value[$key1];
} else {
$key1 = 0;
$key2 = 1;
$dataValue1 = $value;
}
$dataValue2 = array_shift($resultVector);
if (is_array($dataValue2)) {
$dataValue2 = array_shift($dataValue2);
}
$value = [$key1 => $dataValue1, $key2 => $dataValue2];
}
unset($value);
return $lookupVector;
}
private static function verifyResultVector(array $resultVector): array
{
$resultRows = self::rowCount($resultVector);
$resultColumns = self::columnCount($resultVector);
// we correctly orient our results
if ($resultRows === 1 && $resultColumns > 1) {
$resultVector = Matrix::transpose($resultVector);
}
return $resultVector;
}
private static function rowCount(array $dataArray): int
{
return count($dataArray);
}
private static function columnCount(array $dataArray): int
{
$rowKeys = array_keys($dataArray);
$row = array_shift($rowKeys);
return count($dataArray[$row]);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Unique.php 0000644 00000010330 15167673465 0021435 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Unique
{
/**
* UNIQUE
* The UNIQUE function searches for value either from a one-row or one-column range or from an array.
*
* @param mixed $lookupVector The range of cells being searched
* @param mixed $byColumn Whether the uniqueness should be determined by row (the default) or by column
* @param mixed $exactlyOnce Whether the function should return only entries that occur just once in the list
*
* @return mixed The unique values from the search range
*/
public static function unique(mixed $lookupVector, mixed $byColumn = false, mixed $exactlyOnce = false): mixed
{
if (!is_array($lookupVector)) {
// Scalars are always returned "as is"
return $lookupVector;
}
$byColumn = (bool) $byColumn;
$exactlyOnce = (bool) $exactlyOnce;
return ($byColumn === true)
? self::uniqueByColumn($lookupVector, $exactlyOnce)
: self::uniqueByRow($lookupVector, $exactlyOnce);
}
private static function uniqueByRow(array $lookupVector, bool $exactlyOnce): mixed
{
// When not $byColumn, we count whole rows or values, not individual values
// so implode each row into a single string value
array_walk(
$lookupVector,
function (array &$value): void {
$value = implode(chr(0x00), $value);
}
);
$result = self::countValuesCaseInsensitive($lookupVector);
if ($exactlyOnce === true) {
$result = self::exactlyOnceFilter($result);
}
if (count($result) === 0) {
return ExcelError::CALC();
}
$result = array_keys($result);
// restore rows from their strings
array_walk(
$result,
function (string &$value): void {
$value = explode(chr(0x00), $value);
}
);
return (count($result) === 1) ? array_pop($result) : $result;
}
private static function uniqueByColumn(array $lookupVector, bool $exactlyOnce): mixed
{
$flattenedLookupVector = Functions::flattenArray($lookupVector);
if (count($lookupVector, COUNT_RECURSIVE) > count($flattenedLookupVector, COUNT_RECURSIVE) + 1) {
// We're looking at a full column check (multiple rows)
$transpose = Matrix::transpose($lookupVector);
$result = self::uniqueByRow($transpose, $exactlyOnce);
return (is_array($result)) ? Matrix::transpose($result) : $result;
}
$result = self::countValuesCaseInsensitive($flattenedLookupVector);
if ($exactlyOnce === true) {
$result = self::exactlyOnceFilter($result);
}
if (count($result) === 0) {
return ExcelError::CALC();
}
$result = array_keys($result);
return $result;
}
private static function countValuesCaseInsensitive(array $caseSensitiveLookupValues): array
{
$caseInsensitiveCounts = array_count_values(
array_map(
fn (string $value): string => StringHelper::strToUpper($value),
$caseSensitiveLookupValues
)
);
$caseSensitiveCounts = [];
foreach ($caseInsensitiveCounts as $caseInsensitiveKey => $count) {
if (is_numeric($caseInsensitiveKey)) {
$caseSensitiveCounts[$caseInsensitiveKey] = $count;
} else {
foreach ($caseSensitiveLookupValues as $caseSensitiveValue) {
if ($caseInsensitiveKey === StringHelper::strToUpper($caseSensitiveValue)) {
$caseSensitiveCounts[$caseSensitiveValue] = $count;
break;
}
}
}
}
return $caseSensitiveCounts;
}
private static function exactlyOnceFilter(array $values): array
{
return array_filter(
$values,
fn ($value): bool => $value === 1
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php 0000644 00000003644 15167673465 0021426 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Filter
{
public static function filter(array $lookupArray, mixed $matchArray, mixed $ifEmpty = null): mixed
{
if (!is_array($matchArray)) {
return ExcelError::VALUE();
}
$matchArray = self::enumerateArrayKeys($matchArray);
$result = (Matrix::isColumnVector($matchArray))
? self::filterByRow($lookupArray, $matchArray)
: self::filterByColumn($lookupArray, $matchArray);
if (empty($result)) {
return $ifEmpty ?? ExcelError::CALC();
}
return array_values(array_map('array_values', $result));
}
private static function enumerateArrayKeys(array $sortArray): array
{
array_walk(
$sortArray,
function (&$columns): void {
if (is_array($columns)) {
$columns = array_values($columns);
}
}
);
return array_values($sortArray);
}
private static function filterByRow(array $lookupArray, array $matchArray): array
{
$matchArray = array_values(array_column($matchArray, 0));
return array_filter(
array_values($lookupArray),
fn ($index): bool => (bool) $matchArray[$index],
ARRAY_FILTER_USE_KEY
);
}
private static function filterByColumn(array $lookupArray, array $matchArray): array
{
$lookupArray = Matrix::transpose($lookupArray);
if (count($matchArray) === 1) {
$matchArray = array_pop($matchArray);
}
array_walk(
$matchArray,
function (&$value): void {
$value = [$value];
}
);
$result = self::filterByRow($lookupArray, $matchArray);
return Matrix::transpose($result);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php 0000644 00000010171 15167673465 0021571 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class VLookup extends LookupBase
{
use ArrayEnabled;
/**
* VLOOKUP
* The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value
* in the same row based on the index_number.
*
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param mixed $lookupArray The range of cells being searched
* @param mixed $indexNumber The column number in table_array from which the matching value must be returned.
* The first column is 1.
* @param mixed $notExactMatch determines if you are looking for an exact match based on lookup_value
*
* @return mixed The value of the found cell
*/
public static function lookup(mixed $lookupValue, mixed $lookupArray, mixed $indexNumber, mixed $notExactMatch = true): mixed
{
if (is_array($lookupValue) || is_array($indexNumber)) {
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch);
}
$notExactMatch = (bool) ($notExactMatch ?? true);
try {
self::validateLookupArray($lookupArray);
$indexNumber = self::validateIndexLookup($lookupArray, $indexNumber);
} catch (Exception $e) {
return $e->getMessage();
}
$f = array_keys($lookupArray);
$firstRow = array_pop($f);
if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray[$firstRow]))) {
return ExcelError::REF();
}
$columnKeys = array_keys($lookupArray[$firstRow]);
$returnColumn = $columnKeys[--$indexNumber];
$firstColumn = array_shift($columnKeys) ?? 1;
if (!$notExactMatch) {
/** @var callable $callable */
$callable = [self::class, 'vlookupSort'];
uasort($lookupArray, $callable);
}
$rowNumber = self::vLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch);
if ($rowNumber !== null) {
// return the appropriate value
return $lookupArray[$rowNumber][$returnColumn];
}
return ExcelError::NA();
}
private static function vlookupSort(array $a, array $b): int
{
reset($a);
$firstColumn = key($a);
$aLower = StringHelper::strToLower((string) $a[$firstColumn]);
$bLower = StringHelper::strToLower((string) $b[$firstColumn]);
if ($aLower == $bLower) {
return 0;
}
return ($aLower < $bLower) ? -1 : 1;
}
/**
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param int|string $column
*/
private static function vLookupSearch(mixed $lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int
{
$lookupLower = StringHelper::strToLower((string) $lookupValue);
$rowNumber = null;
foreach ($lookupArray as $rowKey => $rowData) {
$bothNumeric = is_numeric($lookupValue) && is_numeric($rowData[$column]);
$bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData[$column]);
$cellDataLower = StringHelper::strToLower((string) $rowData[$column]);
// break if we have passed possible keys
if (
$notExactMatch
&& (($bothNumeric && ($rowData[$column] > $lookupValue))
|| ($bothNotNumeric && ($cellDataLower > $lookupLower)))
) {
break;
}
$rowNumber = self::checkMatch(
$bothNumeric,
$bothNotNumeric,
$notExactMatch,
$rowKey,
$cellDataLower,
$lookupLower,
$rowNumber
);
}
return $rowNumber;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php 0000644 00000002344 15167673465 0021602 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
class Formula
{
/**
* FORMULATEXT.
*
* @param mixed $cellReference The cell to check
* @param ?Cell $cell The current cell (containing this formula)
*/
public static function text(mixed $cellReference = '', ?Cell $cell = null): string
{
if ($cell === null) {
return ExcelError::REF();
}
preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellReference, $matches);
$cellReference = $matches[6] . $matches[7];
$worksheetName = trim($matches[3], "'");
$worksheet = (!empty($worksheetName))
? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheetName)
: $cell->getWorksheet();
if (
$worksheet === null
|| !$worksheet->cellExists($cellReference)
|| !$worksheet->getCell($cellReference)->isFormula()
) {
return ExcelError::NA();
}
return $worksheet->getCell($cellReference)->getValue();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php 0000644 00000010603 15167673465 0021553 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class HLookup extends LookupBase
{
use ArrayEnabled;
/**
* HLOOKUP
* The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value
* in the same column based on the index_number.
*
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param mixed $lookupArray The range of cells being searched
* @param mixed $indexNumber The row number in table_array from which the matching value must be returned.
* The first row is 1.
* @param mixed $notExactMatch determines if you are looking for an exact match based on lookup_value
*
* @return mixed The value of the found cell
*/
public static function lookup(mixed $lookupValue, mixed $lookupArray, mixed $indexNumber, mixed $notExactMatch = true): mixed
{
if (is_array($lookupValue) || is_array($indexNumber)) {
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch);
}
$notExactMatch = (bool) ($notExactMatch ?? true);
try {
self::validateLookupArray($lookupArray);
$lookupArray = self::convertLiteralArray($lookupArray);
$indexNumber = self::validateIndexLookup($lookupArray, $indexNumber);
} catch (Exception $e) {
return $e->getMessage();
}
$f = array_keys($lookupArray);
$firstRow = reset($f);
if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray))) {
return ExcelError::REF();
}
$firstkey = $f[0] - 1;
$returnColumn = $firstkey + $indexNumber;
$firstColumn = array_shift($f) ?? 1;
$rowNumber = self::hLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch);
if ($rowNumber !== null) {
// otherwise return the appropriate value
return $lookupArray[$returnColumn][Coordinate::stringFromColumnIndex($rowNumber)];
}
return ExcelError::NA();
}
/**
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param int|string $column
*/
private static function hLookupSearch(mixed $lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int
{
$lookupLower = StringHelper::strToLower((string) $lookupValue);
$rowNumber = null;
foreach ($lookupArray[$column] as $rowKey => $rowData) {
// break if we have passed possible keys
$bothNumeric = is_numeric($lookupValue) && is_numeric($rowData);
$bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData);
$cellDataLower = StringHelper::strToLower((string) $rowData);
if (
$notExactMatch
&& (($bothNumeric && $rowData > $lookupValue) || ($bothNotNumeric && $cellDataLower > $lookupLower))
) {
break;
}
$rowNumber = self::checkMatch(
$bothNumeric,
$bothNotNumeric,
$notExactMatch,
Coordinate::columnIndexFromString($rowKey),
$cellDataLower,
$lookupLower,
$rowNumber
);
}
return $rowNumber;
}
private static function convertLiteralArray(array $lookupArray): array
{
if (array_key_exists(0, $lookupArray)) {
$lookupArray2 = [];
$row = 0;
foreach ($lookupArray as $arrayVal) {
++$row;
if (!is_array($arrayVal)) {
$arrayVal = [$arrayVal];
}
$arrayVal2 = [];
foreach ($arrayVal as $key2 => $val2) {
$index = Coordinate::stringFromColumnIndex($key2 + 1);
$arrayVal2[$index] = $val2;
}
$lookupArray2[$row] = $arrayVal2;
}
$lookupArray = $lookupArray2;
}
return $lookupArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php 0000644 00000014721 15167673465 0021425 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Offset
{
/**
* OFFSET.
*
* Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
* The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and
* the number of columns to be returned.
*
* Excel Function:
* =OFFSET(cellAddress, rows, cols, [height], [width])
*
* @param null|string $cellAddress The reference from which you want to base the offset.
* Reference must refer to a cell or range of adjacent cells;
* otherwise, OFFSET returns the #VALUE! error value.
* @param mixed $rows The number of rows, up or down, that you want the upper-left cell to refer to.
* Using 5 as the rows argument specifies that the upper-left cell in the
* reference is five rows below reference. Rows can be positive (which means
* below the starting reference) or negative (which means above the starting
* reference).
* @param mixed $columns The number of columns, to the left or right, that you want the upper-left cell
* of the result to refer to. Using 5 as the cols argument specifies that the
* upper-left cell in the reference is five columns to the right of reference.
* Cols can be positive (which means to the right of the starting reference)
* or negative (which means to the left of the starting reference).
* @param mixed $height The height, in number of rows, that you want the returned reference to be.
* Height must be a positive number.
* @param mixed $width The width, in number of columns, that you want the returned reference to be.
* Width must be a positive number.
*
* @return array|string An array containing a cell or range of cells, or a string on error
*/
public static function OFFSET(?string $cellAddress = null, mixed $rows = 0, mixed $columns = 0, mixed $height = null, mixed $width = null, ?Cell $cell = null): string|array
{
$rows = Functions::flattenSingleValue($rows);
$columns = Functions::flattenSingleValue($columns);
$height = Functions::flattenSingleValue($height);
$width = Functions::flattenSingleValue($width);
if ($cellAddress === null || $cellAddress === '') {
return ExcelError::VALUE();
}
if (!is_object($cell)) {
return ExcelError::REF();
}
[$cellAddress, $worksheet] = self::extractWorksheet($cellAddress, $cell);
$startCell = $endCell = $cellAddress;
if (strpos($cellAddress, ':')) {
[$startCell, $endCell] = explode(':', $cellAddress);
}
[$startCellColumn, $startCellRow] = Coordinate::coordinateFromString($startCell);
[$endCellColumn, $endCellRow] = Coordinate::coordinateFromString($endCell);
$startCellRow += $rows;
$startCellColumn = Coordinate::columnIndexFromString($startCellColumn) - 1;
$startCellColumn += $columns;
if (($startCellRow <= 0) || ($startCellColumn < 0)) {
return ExcelError::REF();
}
$endCellColumn = self::adjustEndCellColumnForWidth($endCellColumn, $width, $startCellColumn, $columns);
$startCellColumn = Coordinate::stringFromColumnIndex($startCellColumn + 1);
$endCellRow = self::adustEndCellRowForHeight($height, $startCellRow, $rows, $endCellRow);
if (($endCellRow <= 0) || ($endCellColumn < 0)) {
return ExcelError::REF();
}
$endCellColumn = Coordinate::stringFromColumnIndex($endCellColumn + 1);
$cellAddress = "{$startCellColumn}{$startCellRow}";
if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) {
$cellAddress .= ":{$endCellColumn}{$endCellRow}";
}
return self::extractRequiredCells($worksheet, $cellAddress);
}
private static function extractRequiredCells(?Worksheet $worksheet, string $cellAddress): array
{
return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null)
->extractCellRange($cellAddress, $worksheet, false);
}
private static function extractWorksheet(?string $cellAddress, Cell $cell): array
{
$cellAddress = self::assessCellAddress($cellAddress ?? '', $cell);
$sheetName = '';
if (str_contains($cellAddress, '!')) {
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
$sheetName = trim($sheetName, "'");
}
$worksheet = ($sheetName !== '')
? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($sheetName)
: $cell->getWorksheet();
return [$cellAddress, $worksheet];
}
private static function assessCellAddress(string $cellAddress, Cell $cell): string
{
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $cellAddress) !== false) {
$cellAddress = Functions::expandDefinedName($cellAddress, $cell);
}
return $cellAddress;
}
private static function adjustEndCellColumnForWidth(string $endCellColumn, mixed $width, int $startCellColumn, mixed $columns): int
{
$endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1;
if (($width !== null) && (!is_object($width))) {
$endCellColumn = $startCellColumn + (int) $width - 1;
} else {
$endCellColumn += (int) $columns;
}
return $endCellColumn;
}
private static function adustEndCellRowForHeight(mixed $height, int $startCellRow, mixed $rows, mixed $endCellRow): int
{
if (($height !== null) && (!is_object($height))) {
$endCellRow = $startCellRow + (int) $height - 1;
} else {
$endCellRow += (int) $rows;
}
return $endCellRow;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php 0000644 00000000307 15167673465 0022102 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Internal;
class MakeMatrix
{
/** @param array $args */
public static function make(...$args): array
{
return $args;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php 0000644 00000002350 15167673465 0022546 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Internal;
class WildcardMatch
{
private const SEARCH_SET = [
'~~', // convert double tilde to unprintable value
'~\\*', // convert tilde backslash asterisk to [*] (matches literal asterisk in regexp)
'\\*', // convert backslash asterisk to .* (matches string of any length in regexp)
'~\\?', // convert tilde backslash question to [?] (matches literal question mark in regexp)
'\\?', // convert backslash question to . (matches one character in regexp)
"\x1c", // convert original double tilde to single tilde
];
private const REPLACEMENT_SET = [
"\x1c",
'[*]',
'.*',
'[?]',
'.',
'~',
];
public static function wildcard(string $wildcard): string
{
// Preg Escape the wildcard, but protecting the Excel * and ? search characters
return str_replace(self::SEARCH_SET, self::REPLACEMENT_SET, preg_quote($wildcard, '/'));
}
public static function compare(?string $value, string $wildcard): bool
{
if ($value === '' || $value === null) {
return false;
}
return (bool) preg_match("/^{$wildcard}\$/mui", $value);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Trim.php 0000644 00000003032 15167673465 0020713 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
class Trim
{
use ArrayEnabled;
/**
* CLEAN.
*
* @param mixed $stringValue String Value to check
* Or can be an array of values
*
* @return array|string If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function nonPrintable(mixed $stringValue = '')
{
if (is_array($stringValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $stringValue);
}
$stringValue = Helpers::extractString($stringValue);
return (string) preg_replace('/[\\x00-\\x1f]/', '', "$stringValue");
}
/**
* TRIM.
*
* @param mixed $stringValue String Value to check
* Or can be an array of values
*
* @return array|string If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function spaces(mixed $stringValue = ''): array|string
{
if (is_array($stringValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $stringValue);
}
$stringValue = Helpers::extractString($stringValue);
return trim(preg_replace('/ +/', ' ', trim("$stringValue", ' ')) ?? '', ' ');
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Replace.php 0000644 00000011541 15167673465 0021357 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Replace
{
use ArrayEnabled;
/**
* REPLACE.
*
* @param mixed $oldText The text string value to modify
* Or can be an array of values
* @param mixed $start Integer offset for start character of the replacement
* Or can be an array of values
* @param mixed $chars Integer number of characters to replace from the start offset
* Or can be an array of values
* @param mixed $newText String to replace in the defined position
* Or can be an array of values
*
* @return array|string If an array of values is passed for either of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function replace(mixed $oldText, mixed $start, mixed $chars, mixed $newText): array|string
{
if (is_array($oldText) || is_array($start) || is_array($chars) || is_array($newText)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $oldText, $start, $chars, $newText);
}
try {
$start = Helpers::extractInt($start, 1, 0, true);
$chars = Helpers::extractInt($chars, 0, 0, true);
$oldText = Helpers::extractString($oldText, true);
$newText = Helpers::extractString($newText, true);
$left = StringHelper::substring($oldText, 0, $start - 1);
$right = StringHelper::substring($oldText, $start + $chars - 1, null);
} catch (CalcExp $e) {
return $e->getMessage();
}
$returnValue = $left . $newText . $right;
if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) {
$returnValue = ExcelError::VALUE();
}
return $returnValue;
}
/**
* SUBSTITUTE.
*
* @param mixed $text The text string value to modify
* Or can be an array of values
* @param mixed $fromText The string value that we want to replace in $text
* Or can be an array of values
* @param mixed $toText The string value that we want to replace with in $text
* Or can be an array of values
* @param mixed $instance Integer instance Number for the occurrence of frmText to change
* Or can be an array of values
*
* @return array|string If an array of values is passed for either of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function substitute(mixed $text = '', mixed $fromText = '', mixed $toText = '', mixed $instance = null): array|string
{
if (is_array($text) || is_array($fromText) || is_array($toText) || is_array($instance)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $text, $fromText, $toText, $instance);
}
try {
$text = Helpers::extractString($text, true);
$fromText = Helpers::extractString($fromText, true);
$toText = Helpers::extractString($toText, true);
if ($instance === null) {
$returnValue = str_replace($fromText, $toText, $text);
} else {
if (is_bool($instance)) {
if ($instance === false || Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
return ExcelError::Value();
}
$instance = 1;
}
$instance = Helpers::extractInt($instance, 1, 0, true);
$returnValue = self::executeSubstitution($text, $fromText, $toText, $instance);
}
} catch (CalcExp $e) {
return $e->getMessage();
}
if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) {
$returnValue = ExcelError::VALUE();
}
return $returnValue;
}
private static function executeSubstitution(string $text, string $fromText, string $toText, int $instance): string
{
$pos = -1;
while ($instance > 0) {
$pos = mb_strpos($text, $fromText, $pos + 1, 'UTF-8');
if ($pos === false) {
return $text;
}
--$instance;
}
return Functions::scalar(self::REPLACE($text, ++$pos, StringHelper::countCharacters($fromText), $toText));
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php 0000644 00000004660 15167673465 0021412 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Helpers
{
public static function convertBooleanValue(bool $value): string
{
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
return $value ? '1' : '0';
}
return ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
}
/**
* @param mixed $value String value from which to extract characters
*/
public static function extractString(mixed $value, bool $throwIfError = false): string
{
if (is_bool($value)) {
return self::convertBooleanValue($value);
}
if ($throwIfError && is_string($value) && ErrorValue::isError($value)) {
throw new CalcExp($value);
}
return (string) $value;
}
public static function extractInt(mixed $value, int $minValue, int $gnumericNull = 0, bool $ooBoolOk = false): int
{
if ($value === null) {
// usually 0, but sometimes 1 for Gnumeric
$value = (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_GNUMERIC) ? $gnumericNull : 0;
}
if (is_bool($value) && ($ooBoolOk || Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE)) {
$value = (int) $value;
}
if (!is_numeric($value)) {
throw new CalcExp(ExcelError::VALUE());
}
$value = (int) $value;
if ($value < $minValue) {
throw new CalcExp(ExcelError::VALUE());
}
return (int) $value;
}
public static function extractFloat(mixed $value): float
{
if ($value === null) {
$value = 0.0;
}
if (is_bool($value)) {
$value = (float) $value;
}
if (!is_numeric($value)) {
throw new CalcExp(ExcelError::VALUE());
}
return (float) $value;
}
public static function validateInt(mixed $value): int
{
if ($value === null) {
$value = 0;
} elseif (is_bool($value)) {
$value = (int) $value;
}
return (int) $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php 0000644 00000005125 15167673465 0023242 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class CharacterConvert
{
use ArrayEnabled;
/**
* CHAR.
*
* @param mixed $character Integer Value to convert to its character representation
* Or can be an array of values
*
* @return array|string The character string
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function character(mixed $character): array|string
{
if (is_array($character)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $character);
}
$character = Helpers::validateInt($character);
$min = Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE ? 0 : 1;
if ($character < $min || $character > 255) {
return ExcelError::VALUE();
}
$result = iconv('UCS-4LE', 'UTF-8', pack('V', $character));
return ($result === false) ? '' : $result;
}
/**
* CODE.
*
* @param mixed $characters String character to convert to its ASCII value
* Or can be an array of values
*
* @return array|int|string A string if arguments are invalid
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function code(mixed $characters): array|string|int
{
if (is_array($characters)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $characters);
}
$characters = Helpers::extractString($characters);
if ($characters === '') {
return ExcelError::VALUE();
}
$character = $characters;
if (mb_strlen($characters, 'UTF-8') > 1) {
$character = mb_substr($characters, 0, 1, 'UTF-8');
}
return self::unicodeToOrd($character);
}
private static function unicodeToOrd(string $character): int
{
$retVal = 0;
$iconv = iconv('UTF-8', 'UCS-4LE', $character);
if ($iconv !== false) {
$result = unpack('V', $iconv);
if (is_array($result) && isset($result[1])) {
$retVal = $result[1];
}
}
return $retVal;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Search.php 0000644 00000007165 15167673465 0021220 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Search
{
use ArrayEnabled;
/**
* FIND (case sensitive search).
*
* @param mixed $needle The string to look for
* Or can be an array of values
* @param mixed $haystack The string in which to look
* Or can be an array of values
* @param mixed $offset Integer offset within $haystack to start searching from
* Or can be an array of values
*
* @return array|int|string The offset where the first occurrence of needle was found in the haystack
* If an array of values is passed for the $value or $chars arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function sensitive(mixed $needle, mixed $haystack, mixed $offset = 1): array|string|int
{
if (is_array($needle) || is_array($haystack) || is_array($offset)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $needle, $haystack, $offset);
}
try {
$needle = Helpers::extractString($needle);
$haystack = Helpers::extractString($haystack);
$offset = Helpers::extractInt($offset, 1, 0, true);
} catch (CalcExp $e) {
return $e->getMessage();
}
if (StringHelper::countCharacters($haystack) >= $offset) {
if (StringHelper::countCharacters($needle) === 0) {
return $offset;
}
$pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8');
if ($pos !== false) {
return ++$pos;
}
}
return ExcelError::VALUE();
}
/**
* SEARCH (case insensitive search).
*
* @param mixed $needle The string to look for
* Or can be an array of values
* @param mixed $haystack The string in which to look
* Or can be an array of values
* @param mixed $offset Integer offset within $haystack to start searching from
* Or can be an array of values
*
* @return array|int|string The offset where the first occurrence of needle was found in the haystack
* If an array of values is passed for the $value or $chars arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function insensitive(mixed $needle, mixed $haystack, mixed $offset = 1): array|string|int
{
if (is_array($needle) || is_array($haystack) || is_array($offset)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $needle, $haystack, $offset);
}
try {
$needle = Helpers::extractString($needle);
$haystack = Helpers::extractString($haystack);
$offset = Helpers::extractInt($offset, 1, 0, true);
} catch (CalcExp $e) {
return $e->getMessage();
}
if (StringHelper::countCharacters($haystack) >= $offset) {
if (StringHelper::countCharacters($needle) === 0) {
return $offset;
}
$pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8');
if ($pos !== false) {
return ++$pos;
}
}
return ExcelError::VALUE();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php 0000644 00000011133 15167673465 0022225 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Concatenate
{
use ArrayEnabled;
/**
* CONCATENATE.
*
* @param array $args
*/
public static function CONCATENATE(...$args): string
{
$returnValue = '';
// Loop through arguments
$aArgs = Functions::flattenArray($args);
foreach ($aArgs as $arg) {
$value = Helpers::extractString($arg);
if (ErrorValue::isError($value)) {
$returnValue = $value;
break;
}
$returnValue .= Helpers::extractString($arg);
if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) {
$returnValue = ExcelError::CALC();
break;
}
}
return $returnValue;
}
/**
* TEXTJOIN.
*
* @param mixed $delimiter The delimter to use between the joined arguments
* Or can be an array of values
* @param mixed $ignoreEmpty true/false Flag indicating whether empty arguments should be skipped
* Or can be an array of values
* @param mixed $args The values to join
*
* @return array|string The joined string
* If an array of values is passed for the $delimiter or $ignoreEmpty arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function TEXTJOIN(mixed $delimiter = '', mixed $ignoreEmpty = true, mixed ...$args): array|string
{
if (is_array($delimiter) || is_array($ignoreEmpty)) {
return self::evaluateArrayArgumentsSubset(
[self::class, __FUNCTION__],
2,
$delimiter,
$ignoreEmpty,
...$args
);
}
$delimiter ??= '';
$ignoreEmpty ??= true;
$aArgs = Functions::flattenArray($args);
$returnValue = self::evaluateTextJoinArray($ignoreEmpty, $aArgs);
$returnValue ??= implode($delimiter, $aArgs);
if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) {
$returnValue = ExcelError::CALC();
}
return $returnValue;
}
private static function evaluateTextJoinArray(bool $ignoreEmpty, array &$aArgs): ?string
{
foreach ($aArgs as $key => &$arg) {
$value = Helpers::extractString($arg);
if (ErrorValue::isError($value)) {
return $value;
}
if ($ignoreEmpty === true && ((is_string($arg) && trim($arg) === '') || $arg === null)) {
unset($aArgs[$key]);
} elseif (is_bool($arg)) {
$arg = Helpers::convertBooleanValue($arg);
}
}
return null;
}
/**
* REPT.
*
* Returns the result of builtin function round after validating args.
*
* @param mixed $stringValue The value to repeat
* Or can be an array of values
* @param mixed $repeatCount The number of times the string value should be repeated
* Or can be an array of values
*
* @return array|string The repeated string
* If an array of values is passed for the $stringValue or $repeatCount arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function builtinREPT(mixed $stringValue, mixed $repeatCount): array|string
{
if (is_array($stringValue) || is_array($repeatCount)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $stringValue, $repeatCount);
}
$stringValue = Helpers::extractString($stringValue);
if (!is_numeric($repeatCount) || $repeatCount < 0) {
$returnValue = ExcelError::VALUE();
} elseif (ErrorValue::isError($stringValue)) {
$returnValue = $stringValue;
} else {
$returnValue = str_repeat($stringValue, (int) $repeatCount);
if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) {
$returnValue = ExcelError::VALUE(); // note VALUE not CALC
}
}
return $returnValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php 0000644 00000004757 15167673465 0022233 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class CaseConvert
{
use ArrayEnabled;
/**
* LOWERCASE.
*
* Converts a string value to upper case.
*
* @param mixed $mixedCaseValue The string value to convert to lower case
* Or can be an array of values
*
* @return array|string If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function lower(mixed $mixedCaseValue): array|string
{
if (is_array($mixedCaseValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
}
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
return StringHelper::strToLower($mixedCaseValue);
}
/**
* UPPERCASE.
*
* Converts a string value to upper case.
*
* @param mixed $mixedCaseValue The string value to convert to upper case
* Or can be an array of values
*
* @return array|string If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function upper(mixed $mixedCaseValue): array|string
{
if (is_array($mixedCaseValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
}
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
return StringHelper::strToUpper($mixedCaseValue);
}
/**
* PROPERCASE.
*
* Converts a string value to proper or title case.
*
* @param mixed $mixedCaseValue The string value to convert to title case
* Or can be an array of values
*
* @return array|string If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function proper(mixed $mixedCaseValue): array|string
{
if (is_array($mixedCaseValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
}
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
return StringHelper::strToTitle($mixedCaseValue);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php 0000644 00000020433 15167673465 0020730 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
class Text
{
use ArrayEnabled;
/**
* LEN.
*
* @param mixed $value String Value
* Or can be an array of values
*
* @return array|int If an array of values is passed for the argument, then the returned result
* will also be an array with matching dimensions
*/
public static function length(mixed $value = ''): array|int
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
$value = Helpers::extractString($value);
return mb_strlen($value, 'UTF-8');
}
/**
* Compares two text strings and returns TRUE if they are exactly the same, FALSE otherwise.
* EXACT is case-sensitive but ignores formatting differences.
* Use EXACT to test text being entered into a document.
*
* @param mixed $value1 String Value
* Or can be an array of values
* @param mixed $value2 String Value
* Or can be an array of values
*
* @return array|bool If an array of values is passed for either of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function exact(mixed $value1, mixed $value2): array|bool
{
if (is_array($value1) || is_array($value2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value1, $value2);
}
$value1 = Helpers::extractString($value1);
$value2 = Helpers::extractString($value2);
return $value2 === $value1;
}
/**
* T.
*
* @param mixed $testValue Value to check
* Or can be an array of values
*
* @return array|string If an array of values is passed for the argument, then the returned result
* will also be an array with matching dimensions
*/
public static function test(mixed $testValue = ''): array|string
{
if (is_array($testValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $testValue);
}
if (is_string($testValue)) {
return $testValue;
}
return '';
}
/**
* TEXTSPLIT.
*
* @param mixed $text the text that you're searching
* @param null|array|string $columnDelimiter The text that marks the point where to spill the text across columns.
* Multiple delimiters can be passed as an array of string values
* @param null|array|string $rowDelimiter The text that marks the point where to spill the text down rows.
* Multiple delimiters can be passed as an array of string values
* @param bool $ignoreEmpty Specify FALSE to create an empty cell when two delimiters are consecutive.
* true = create empty cells
* false = skip empty cells
* Defaults to TRUE, which creates an empty cell
* @param bool $matchMode Determines whether the match is case-sensitive or not.
* true = case-sensitive
* false = case-insensitive
* By default, a case-sensitive match is done.
* @param mixed $padding The value with which to pad the result.
* The default is #N/A.
*
* @return array the array built from the text, split by the row and column delimiters
*/
public static function split(mixed $text, $columnDelimiter = null, $rowDelimiter = null, bool $ignoreEmpty = false, bool $matchMode = true, mixed $padding = '#N/A'): array
{
$text = Functions::flattenSingleValue($text);
$flags = self::matchFlags($matchMode);
if ($rowDelimiter !== null) {
$delimiter = self::buildDelimiter($rowDelimiter);
$rows = ($delimiter === '()')
? [$text]
: preg_split("/{$delimiter}/{$flags}", $text);
} else {
$rows = [$text];
}
/** @var array $rows */
if ($ignoreEmpty === true) {
$rows = array_values(array_filter(
$rows,
fn ($row): bool => $row !== ''
));
}
if ($columnDelimiter !== null) {
$delimiter = self::buildDelimiter($columnDelimiter);
array_walk(
$rows,
function (&$row) use ($delimiter, $flags, $ignoreEmpty): void {
$row = ($delimiter === '()')
? [$row]
: preg_split("/{$delimiter}/{$flags}", $row);
/** @var array $row */
if ($ignoreEmpty === true) {
$row = array_values(array_filter(
$row,
fn ($value): bool => $value !== ''
));
}
}
);
if ($ignoreEmpty === true) {
$rows = array_values(array_filter(
$rows,
fn ($row): bool => $row !== [] && $row !== ['']
));
}
}
return self::applyPadding($rows, $padding);
}
private static function applyPadding(array $rows, mixed $padding): array
{
$columnCount = array_reduce(
$rows,
fn (int $counter, array $row): int => max($counter, count($row)),
0
);
return array_map(
function (array $row) use ($columnCount, $padding): array {
return (count($row) < $columnCount)
? array_merge($row, array_fill(0, $columnCount - count($row), $padding))
: $row;
},
$rows
);
}
/**
* @param null|array|string $delimiter the text that marks the point before which you want to split
* Multiple delimiters can be passed as an array of string values
*/
private static function buildDelimiter($delimiter): string
{
$valueSet = Functions::flattenArray($delimiter);
if (is_array($delimiter) && count($valueSet) > 1) {
$quotedDelimiters = array_map(
fn ($delimiter): string => preg_quote($delimiter ?? '', '/'),
$valueSet
);
$delimiters = implode('|', $quotedDelimiters);
return '(' . $delimiters . ')';
}
return '(' . preg_quote(Functions::flattenSingleValue($delimiter), '/') . ')';
}
private static function matchFlags(bool $matchMode): string
{
return ($matchMode === true) ? 'miu' : 'mu';
}
public static function fromArray(array $array, int $format = 0): string
{
$result = [];
foreach ($array as $row) {
$cells = [];
foreach ($row as $cellValue) {
$value = ($format === 1) ? self::formatValueMode1($cellValue) : self::formatValueMode0($cellValue);
$cells[] = $value;
}
$result[] = implode(($format === 1) ? ',' : ', ', $cells);
}
$result = implode(($format === 1) ? ';' : ', ', $result);
return ($format === 1) ? '{' . $result . '}' : $result;
}
private static function formatValueMode0(mixed $cellValue): string
{
if (is_bool($cellValue)) {
return Calculation::getLocaleBoolean($cellValue ? 'TRUE' : 'FALSE');
}
return (string) $cellValue;
}
private static function formatValueMode1(mixed $cellValue): string
{
if (is_string($cellValue) && ErrorValue::isError($cellValue) === false) {
return Calculation::FORMULA_STRING_QUOTE . $cellValue . Calculation::FORMULA_STRING_QUOTE;
} elseif (is_bool($cellValue)) {
return Calculation::getLocaleBoolean($cellValue ? 'TRUE' : 'FALSE');
}
return (string) $cellValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php 0000644 00000027554 15167673465 0021431 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Extract
{
use ArrayEnabled;
/**
* LEFT.
*
* @param mixed $value String value from which to extract characters
* Or can be an array of values
* @param mixed $chars The number of characters to extract (as an integer)
* Or can be an array of values
*
* @return array|string The joined string
* If an array of values is passed for the $value or $chars arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function left(mixed $value, mixed $chars = 1): array|string
{
if (is_array($value) || is_array($chars)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $chars);
}
try {
$value = Helpers::extractString($value);
$chars = Helpers::extractInt($chars, 0, 1);
} catch (CalcExp $e) {
return $e->getMessage();
}
return mb_substr($value, 0, $chars, 'UTF-8');
}
/**
* MID.
*
* @param mixed $value String value from which to extract characters
* Or can be an array of values
* @param mixed $start Integer offset of the first character that we want to extract
* Or can be an array of values
* @param mixed $chars The number of characters to extract (as an integer)
* Or can be an array of values
*
* @return array|string The joined string
* If an array of values is passed for the $value, $start or $chars arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function mid(mixed $value, mixed $start, mixed $chars): array|string
{
if (is_array($value) || is_array($start) || is_array($chars)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $start, $chars);
}
try {
$value = Helpers::extractString($value);
$start = Helpers::extractInt($start, 1);
$chars = Helpers::extractInt($chars, 0);
} catch (CalcExp $e) {
return $e->getMessage();
}
return mb_substr($value, --$start, $chars, 'UTF-8');
}
/**
* RIGHT.
*
* @param mixed $value String value from which to extract characters
* Or can be an array of values
* @param mixed $chars The number of characters to extract (as an integer)
* Or can be an array of values
*
* @return array|string The joined string
* If an array of values is passed for the $value or $chars arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function right(mixed $value, mixed $chars = 1): array|string
{
if (is_array($value) || is_array($chars)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $chars);
}
try {
$value = Helpers::extractString($value);
$chars = Helpers::extractInt($chars, 0, 1);
} catch (CalcExp $e) {
return $e->getMessage();
}
return mb_substr($value, mb_strlen($value, 'UTF-8') - $chars, $chars, 'UTF-8');
}
/**
* TEXTBEFORE.
*
* @param mixed $text the text that you're searching
* Or can be an array of values
* @param null|array|string $delimiter the text that marks the point before which you want to extract
* Multiple delimiters can be passed as an array of string values
* @param mixed $instance The instance of the delimiter after which you want to extract the text.
* By default, this is the first instance (1).
* A negative value means start searching from the end of the text string.
* Or can be an array of values
* @param mixed $matchMode Determines whether the match is case-sensitive or not.
* 0 - Case-sensitive
* 1 - Case-insensitive
* Or can be an array of values
* @param mixed $matchEnd Treats the end of text as a delimiter.
* 0 - Don't match the delimiter against the end of the text.
* 1 - Match the delimiter against the end of the text.
* Or can be an array of values
* @param mixed $ifNotFound value to return if no match is found
* The default is a #N/A Error
* Or can be an array of values
*
* @return array|string the string extracted from text before the delimiter; or the $ifNotFound value
* If an array of values is passed for any of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function before(mixed $text, $delimiter, mixed $instance = 1, mixed $matchMode = 0, mixed $matchEnd = 0, mixed $ifNotFound = '#N/A'): array|string
{
if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) {
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
}
$text = Helpers::extractString($text ?? '');
$instance = (int) $instance;
$matchMode = (int) $matchMode;
$matchEnd = (int) $matchEnd;
$split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
if (is_string($split)) {
return $split;
}
if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') {
return ($instance > 0) ? '' : $text;
}
// Adjustment for a match as the first element of the split
$flags = self::matchFlags($matchMode);
$delimiter = self::buildDelimiter($delimiter);
$adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]);
$oddReverseAdjustment = count($split) % 2;
$split = ($instance < 0)
? array_slice($split, 0, max(count($split) - (abs($instance) * 2 - 1) - $adjust - $oddReverseAdjustment, 0))
: array_slice($split, 0, $instance * 2 - 1 - $adjust);
return implode('', $split);
}
/**
* TEXTAFTER.
*
* @param mixed $text the text that you're searching
* @param null|array|string $delimiter the text that marks the point before which you want to extract
* Multiple delimiters can be passed as an array of string values
* @param mixed $instance The instance of the delimiter after which you want to extract the text.
* By default, this is the first instance (1).
* A negative value means start searching from the end of the text string.
* Or can be an array of values
* @param mixed $matchMode Determines whether the match is case-sensitive or not.
* 0 - Case-sensitive
* 1 - Case-insensitive
* Or can be an array of values
* @param mixed $matchEnd Treats the end of text as a delimiter.
* 0 - Don't match the delimiter against the end of the text.
* 1 - Match the delimiter against the end of the text.
* Or can be an array of values
* @param mixed $ifNotFound value to return if no match is found
* The default is a #N/A Error
* Or can be an array of values
*
* @return array|string the string extracted from text before the delimiter; or the $ifNotFound value
* If an array of values is passed for any of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function after(mixed $text, $delimiter, mixed $instance = 1, mixed $matchMode = 0, mixed $matchEnd = 0, mixed $ifNotFound = '#N/A'): array|string
{
if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) {
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
}
$text = Helpers::extractString($text ?? '');
$instance = (int) $instance;
$matchMode = (int) $matchMode;
$matchEnd = (int) $matchEnd;
$split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
if (is_string($split)) {
return $split;
}
if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') {
return ($instance < 0) ? '' : $text;
}
// Adjustment for a match as the first element of the split
$flags = self::matchFlags($matchMode);
$delimiter = self::buildDelimiter($delimiter);
$adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]);
$oddReverseAdjustment = count($split) % 2;
$split = ($instance < 0)
? array_slice($split, count($split) - ((int) abs($instance + 1) * 2) - $adjust - $oddReverseAdjustment)
: array_slice($split, $instance * 2 - $adjust);
return implode('', $split);
}
private static function validateTextBeforeAfter(string $text, null|array|string $delimiter, int $instance, int $matchMode, int $matchEnd, mixed $ifNotFound): array|string
{
$flags = self::matchFlags($matchMode);
$delimiter = self::buildDelimiter($delimiter);
if (preg_match('/' . $delimiter . "/{$flags}", $text) === 0 && $matchEnd === 0) {
return $ifNotFound;
}
$split = preg_split('/' . $delimiter . "/{$flags}", $text, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
if ($split === false) {
return ExcelError::NA();
}
if ($instance === 0 || abs($instance) > StringHelper::countCharacters($text)) {
return ExcelError::VALUE();
}
if ($matchEnd === 0 && (abs($instance) > floor(count($split) / 2))) {
return ExcelError::NA();
} elseif ($matchEnd !== 0 && (abs($instance) - 1 > ceil(count($split) / 2))) {
return ExcelError::NA();
}
return $split;
}
/**
* @param null|array|string $delimiter the text that marks the point before which you want to extract
* Multiple delimiters can be passed as an array of string values
*/
private static function buildDelimiter($delimiter): string
{
if (is_array($delimiter)) {
$delimiter = Functions::flattenArray($delimiter);
$quotedDelimiters = array_map(
fn ($delimiter): string => preg_quote($delimiter ?? '', '/'),
$delimiter
);
$delimiters = implode('|', $quotedDelimiters);
return '(' . $delimiters . ')';
}
return '(' . preg_quote($delimiter ?? '', '/') . ')';
}
private static function matchFlags(int $matchMode): string
{
return ($matchMode === 0) ? 'mu' : 'miu';
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php 0000644 00000027307 15167673465 0021243 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use DateTimeInterface;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class Format
{
use ArrayEnabled;
/**
* DOLLAR.
*
* This function converts a number to text using currency format, with the decimals rounded to the specified place.
* The format used is $#,##0.00_);($#,##0.00)..
*
* @param mixed $value The value to format
* Or can be an array of values
* @param mixed $decimals The number of digits to display to the right of the decimal point (as an integer).
* If decimals is negative, number is rounded to the left of the decimal point.
* If you omit decimals, it is assumed to be 2
* Or can be an array of values
*
* @return array|string If an array of values is passed for either of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function DOLLAR(mixed $value = 0, mixed $decimals = 2)
{
if (is_array($value) || is_array($decimals)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimals);
}
try {
$value = Helpers::extractFloat($value);
$decimals = Helpers::extractInt($decimals, -100, 0, true);
} catch (CalcExp $e) {
return $e->getMessage();
}
$mask = '$#,##0';
if ($decimals > 0) {
$mask .= '.' . str_repeat('0', $decimals);
} else {
$round = 10 ** abs($decimals);
if ($value < 0) {
$round = 0 - $round;
}
/** @var float|int|string */
$value = MathTrig\Round::multiple($value, $round);
}
$mask = "{$mask};-{$mask}";
return NumberFormat::toFormattedString($value, $mask);
}
/**
* FIXED.
*
* @param mixed $value The value to format
* Or can be an array of values
* @param mixed $decimals Integer value for the number of decimal places that should be formatted
* Or can be an array of values
* @param mixed $noCommas Boolean value indicating whether the value should have thousands separators or not
* Or can be an array of values
*
* @return array|string If an array of values is passed for either of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function FIXEDFORMAT(mixed $value, mixed $decimals = 2, mixed $noCommas = false): array|string
{
if (is_array($value) || is_array($decimals) || is_array($noCommas)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimals, $noCommas);
}
try {
$value = Helpers::extractFloat($value);
$decimals = Helpers::extractInt($decimals, -100, 0, true);
} catch (CalcExp $e) {
return $e->getMessage();
}
$valueResult = round($value, $decimals);
if ($decimals < 0) {
$decimals = 0;
}
if ($noCommas === false) {
$valueResult = number_format(
$valueResult,
$decimals,
StringHelper::getDecimalSeparator(),
StringHelper::getThousandsSeparator()
);
}
return (string) $valueResult;
}
/**
* TEXT.
*
* @param mixed $value The value to format
* Or can be an array of values
* @param mixed $format A string with the Format mask that should be used
* Or can be an array of values
*
* @return array|string If an array of values is passed for either of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function TEXTFORMAT(mixed $value, mixed $format): array|string
{
if (is_array($value) || is_array($format)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format);
}
$value = Helpers::extractString($value);
$format = Helpers::extractString($format);
$format = (string) NumberFormat::convertSystemFormats($format);
if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) {
$value1 = DateTimeExcel\DateValue::fromString($value);
$value2 = DateTimeExcel\TimeValue::fromString($value);
/** @var float|int|string */
$value = (is_numeric($value1) && is_numeric($value2)) ? ($value1 + $value2) : (is_numeric($value1) ? $value2 : $value1);
}
return (string) NumberFormat::toFormattedString($value, $format);
}
/**
* @param mixed $value Value to check
*/
private static function convertValue(mixed $value, bool $spacesMeanZero = false): mixed
{
$value = $value ?? 0;
if (is_bool($value)) {
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
$value = (int) $value;
} else {
throw new CalcExp(ExcelError::VALUE());
}
}
if (is_string($value)) {
$value = trim($value);
if ($spacesMeanZero && $value === '') {
$value = 0;
}
}
return $value;
}
/**
* VALUE.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|DateTimeInterface|float|int|string A string if arguments are invalid
* If an array of values is passed for the argument, then the returned result
* will also be an array with matching dimensions
*/
public static function VALUE(mixed $value = '')
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = self::convertValue($value);
} catch (CalcExp $e) {
return $e->getMessage();
}
if (!is_numeric($value)) {
$numberValue = str_replace(
StringHelper::getThousandsSeparator(),
'',
trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode())
);
if ($numberValue === '') {
return ExcelError::VALUE();
}
if (is_numeric($numberValue)) {
return (float) $numberValue;
}
$dateSetting = Functions::getReturnDateType();
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
if (str_contains($value, ':')) {
$timeValue = Functions::scalar(DateTimeExcel\TimeValue::fromString($value));
if ($timeValue !== ExcelError::VALUE()) {
Functions::setReturnDateType($dateSetting);
return $timeValue;
}
}
$dateValue = Functions::scalar(DateTimeExcel\DateValue::fromString($value));
if ($dateValue !== ExcelError::VALUE()) {
Functions::setReturnDateType($dateSetting);
return $dateValue;
}
Functions::setReturnDateType($dateSetting);
return ExcelError::VALUE();
}
return (float) $value;
}
/**
* TEXT.
*
* @param mixed $value The value to format
* Or can be an array of values
*
* @return array|string If an array of values is passed for either of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function valueToText(mixed $value, mixed $format = false): array|string
{
if (is_array($value) || is_array($format)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format);
}
$format = (bool) $format;
if (is_object($value) && $value instanceof RichText) {
$value = $value->getPlainText();
}
if (is_string($value)) {
$value = ($format === true) ? Calculation::wrapResult($value) : $value;
$value = str_replace("\n", '', $value);
} elseif (is_bool($value)) {
$value = Calculation::getLocaleBoolean($value ? 'TRUE' : 'FALSE');
}
return (string) $value;
}
private static function getDecimalSeparator(mixed $decimalSeparator): string
{
return empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : (string) $decimalSeparator;
}
private static function getGroupSeparator(mixed $groupSeparator): string
{
return empty($groupSeparator) ? StringHelper::getThousandsSeparator() : (string) $groupSeparator;
}
/**
* NUMBERVALUE.
*
* @param mixed $value The value to format
* Or can be an array of values
* @param mixed $decimalSeparator A string with the decimal separator to use, defaults to locale defined value
* Or can be an array of values
* @param mixed $groupSeparator A string with the group/thousands separator to use, defaults to locale defined value
* Or can be an array of values
*/
public static function NUMBERVALUE(mixed $value = '', mixed $decimalSeparator = null, mixed $groupSeparator = null): array|string|float
{
if (is_array($value) || is_array($decimalSeparator) || is_array($groupSeparator)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimalSeparator, $groupSeparator);
}
try {
$value = self::convertValue($value, true);
$decimalSeparator = self::getDecimalSeparator($decimalSeparator);
$groupSeparator = self::getGroupSeparator($groupSeparator);
} catch (CalcExp $e) {
return $e->getMessage();
}
if (!is_numeric($value)) {
$decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator, '/') . '/', $value, $matches, PREG_OFFSET_CAPTURE);
if ($decimalPositions > 1) {
return ExcelError::VALUE();
}
$decimalOffset = array_pop($matches[0])[1] ?? null;
if ($decimalOffset === null || strpos($value, $groupSeparator, $decimalOffset) !== false) {
return ExcelError::VALUE();
}
$value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value);
// Handle the special case of trailing % signs
$percentageString = rtrim($value, '%');
if (!is_numeric($percentageString)) {
return ExcelError::VALUE();
}
$percentageAdjustment = strlen($value) - strlen($percentageString);
if ($percentageAdjustment) {
$value = (float) $percentageString;
$value /= 10 ** ($percentageAdjustment * 2);
}
}
return is_array($value) ? ExcelError::VALUE() : (float) $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Web/Service.php 0000644 00000003643 15167673465 0020407 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Web;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Settings;
use Psr\Http\Client\ClientExceptionInterface;
class Service
{
/**
* WEBSERVICE.
*
* Returns data from a web service on the Internet or Intranet.
*
* Excel Function:
* Webservice(url)
*
* @return string the output resulting from a call to the webservice
*/
public static function webService(string $url): string
{
$url = trim($url);
if (strlen($url) > 2048) {
return ExcelError::VALUE(); // Invalid URL length
}
if (!preg_match('/^http[s]?:\/\//', $url)) {
return ExcelError::VALUE(); // Invalid protocol
}
// Get results from the the webservice
$client = Settings::getHttpClient();
$requestFactory = Settings::getRequestFactory();
$request = $requestFactory->createRequest('GET', $url);
try {
$response = $client->sendRequest($request);
} catch (ClientExceptionInterface) {
return ExcelError::VALUE(); // cURL error
}
if ($response->getStatusCode() != 200) {
return ExcelError::VALUE(); // cURL error
}
$output = $response->getBody()->getContents();
if (strlen($output) > 32767) {
return ExcelError::VALUE(); // Output not a string or too long
}
return $output;
}
/**
* URLENCODE.
*
* Returns data from a web service on the Internet or Intranet.
*
* Excel Function:
* urlEncode(text)
*
* @return string the url encoded output
*/
public static function urlEncode(mixed $text): string
{
if (!is_string($text)) {
return ExcelError::VALUE();
}
return str_replace('+', '%20', urlencode($text));
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php 0000644 00000007774 15167673465 0020711 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
/**
* PARTLY BASED ON:
* Copyright (c) 2007 E. W. Bachtal, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* The software is provided "as is", without warranty of any kind, express or implied, including but not
* limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In
* no event shall the authors or copyright holders be liable for any claim, damages or other liability,
* whether in an action of contract, tort or otherwise, arising from, out of or in connection with the
* software or the use or other dealings in the software.
*
* https://ewbi.blogs.com/develops/2007/03/excel_formula_p.html
* https://ewbi.blogs.com/develops/2004/12/excel_formula_p.html
*/
class FormulaToken
{
// Token types
const TOKEN_TYPE_NOOP = 'Noop';
const TOKEN_TYPE_OPERAND = 'Operand';
const TOKEN_TYPE_FUNCTION = 'Function';
const TOKEN_TYPE_SUBEXPRESSION = 'Subexpression';
const TOKEN_TYPE_ARGUMENT = 'Argument';
const TOKEN_TYPE_OPERATORPREFIX = 'OperatorPrefix';
const TOKEN_TYPE_OPERATORINFIX = 'OperatorInfix';
const TOKEN_TYPE_OPERATORPOSTFIX = 'OperatorPostfix';
const TOKEN_TYPE_WHITESPACE = 'Whitespace';
const TOKEN_TYPE_UNKNOWN = 'Unknown';
// Token subtypes
const TOKEN_SUBTYPE_NOTHING = 'Nothing';
const TOKEN_SUBTYPE_START = 'Start';
const TOKEN_SUBTYPE_STOP = 'Stop';
const TOKEN_SUBTYPE_TEXT = 'Text';
const TOKEN_SUBTYPE_NUMBER = 'Number';
const TOKEN_SUBTYPE_LOGICAL = 'Logical';
const TOKEN_SUBTYPE_ERROR = 'Error';
const TOKEN_SUBTYPE_RANGE = 'Range';
const TOKEN_SUBTYPE_MATH = 'Math';
const TOKEN_SUBTYPE_CONCATENATION = 'Concatenation';
const TOKEN_SUBTYPE_INTERSECTION = 'Intersection';
const TOKEN_SUBTYPE_UNION = 'Union';
/**
* Value.
*/
private string $value;
/**
* Token Type (represented by TOKEN_TYPE_*).
*/
private string $tokenType;
/**
* Token SubType (represented by TOKEN_SUBTYPE_*).
*/
private string $tokenSubType;
/**
* Create a new FormulaToken.
*
* @param string $tokenType Token type (represented by TOKEN_TYPE_*)
* @param string $tokenSubType Token Subtype (represented by TOKEN_SUBTYPE_*)
*/
public function __construct(string $value, string $tokenType = self::TOKEN_TYPE_UNKNOWN, string $tokenSubType = self::TOKEN_SUBTYPE_NOTHING)
{
// Initialise values
$this->value = $value;
$this->tokenType = $tokenType;
$this->tokenSubType = $tokenSubType;
}
/**
* Get Value.
*/
public function getValue(): string
{
return $this->value;
}
/**
* Set Value.
*/
public function setValue(string $value): void
{
$this->value = $value;
}
/**
* Get Token Type (represented by TOKEN_TYPE_*).
*/
public function getTokenType(): string
{
return $this->tokenType;
}
/**
* Set Token Type (represented by TOKEN_TYPE_*).
*/
public function setTokenType(string $value): void
{
$this->tokenType = $value;
}
/**
* Get Token SubType (represented by TOKEN_SUBTYPE_*).
*/
public function getTokenSubType(): string
{
return $this->tokenSubType;
}
/**
* Set Token SubType (represented by TOKEN_SUBTYPE_*).
*/
public function setTokenSubType(string $value): void
{
$this->tokenSubType = $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Token/Stack.php 0000644 00000005600 15167673465 0020412 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Token;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\BranchPruner;
class Stack
{
private BranchPruner $branchPruner;
/**
* The parser stack for formulae.
*
* @var mixed[]
*/
private array $stack = [];
/**
* Count of entries in the parser stack.
*/
private int $count = 0;
public function __construct(BranchPruner $branchPruner)
{
$this->branchPruner = $branchPruner;
}
/**
* Return the number of entries on the stack.
*/
public function count(): int
{
return $this->count;
}
/**
* Push a new entry onto the stack.
*/
public function push(string $type, mixed $value, ?string $reference = null): void
{
$stackItem = $this->getStackItem($type, $value, $reference);
$this->stack[$this->count++] = $stackItem;
if ($type === 'Function') {
$localeFunction = Calculation::localeFunc($value);
if ($localeFunction != $value) {
$this->stack[($this->count - 1)]['localeValue'] = $localeFunction;
}
}
}
public function pushStackItem(array $stackItem): void
{
$this->stack[$this->count++] = $stackItem;
}
public function getStackItem(string $type, mixed $value, ?string $reference = null): array
{
$stackItem = [
'type' => $type,
'value' => $value,
'reference' => $reference,
];
// will store the result under this alias
$storeKey = $this->branchPruner->currentCondition();
if (isset($storeKey) || $reference === 'NULL') {
$stackItem['storeKey'] = $storeKey;
}
// will only run computation if the matching store key is true
$onlyIf = $this->branchPruner->currentOnlyIf();
if (isset($onlyIf) || $reference === 'NULL') {
$stackItem['onlyIf'] = $onlyIf;
}
// will only run computation if the matching store key is false
$onlyIfNot = $this->branchPruner->currentOnlyIfNot();
if (isset($onlyIfNot) || $reference === 'NULL') {
$stackItem['onlyIfNot'] = $onlyIfNot;
}
return $stackItem;
}
/**
* Pop the last entry from the stack.
*/
public function pop(): ?array
{
if ($this->count > 0) {
return $this->stack[--$this->count];
}
return null;
}
/**
* Return an entry from the stack without removing it.
*/
public function last(int $n = 1): ?array
{
if ($this->count - $n < 0) {
return null;
}
return $this->stack[$this->count - $n];
}
/**
* Clear the stack.
*/
public function clear(): void
{
$this->stack = [];
$this->count = 0;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php 0000644 00000000773 15167673465 0020231 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class Exception extends PhpSpreadsheetException
{
public const CALCULATION_ENGINE_PUSH_TO_STACK = 1;
/**
* Error handler callback.
*/
public static function errorHandlerCallback(int $code, string $string, string $file, int $line): void
{
$e = new self($string, $code);
$e->line = $line;
$e->file = $file;
throw $e;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php 0000644 00000053620 15167673465 0021054 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
/**
* PARTLY BASED ON:
* Copyright (c) 2007 E. W. Bachtal, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* The software is provided "as is", without warranty of any kind, express or implied, including but not
* limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In
* no event shall the authors or copyright holders be liable for any claim, damages or other liability,
* whether in an action of contract, tort or otherwise, arising from, out of or in connection with the
* software or the use or other dealings in the software.
*
* https://ewbi.blogs.com/develops/2007/03/excel_formula_p.html
* https://ewbi.blogs.com/develops/2004/12/excel_formula_p.html
*/
class FormulaParser
{
// Character constants
const QUOTE_DOUBLE = '"';
const QUOTE_SINGLE = '\'';
const BRACKET_CLOSE = ']';
const BRACKET_OPEN = '[';
const BRACE_OPEN = '{';
const BRACE_CLOSE = '}';
const PAREN_OPEN = '(';
const PAREN_CLOSE = ')';
const SEMICOLON = ';';
const WHITESPACE = ' ';
const COMMA = ',';
const ERROR_START = '#';
const OPERATORS_SN = '+-';
const OPERATORS_INFIX = '+-*/^&=><';
const OPERATORS_POSTFIX = '%';
/**
* Formula.
*/
private string $formula;
/**
* Tokens.
*
* @var FormulaToken[]
*/
private array $tokens = [];
/**
* Create a new FormulaParser.
*
* @param ?string $formula Formula to parse
*/
public function __construct(?string $formula = '')
{
// Check parameters
if ($formula === null) {
throw new Exception('Invalid parameter passed: formula');
}
// Initialise values
$this->formula = trim($formula);
// Parse!
$this->parseToTokens();
}
/**
* Get Formula.
*/
public function getFormula(): string
{
return $this->formula;
}
/**
* Get Token.
*
* @param int $id Token id
*/
public function getToken(int $id = 0): FormulaToken
{
if (isset($this->tokens[$id])) {
return $this->tokens[$id];
}
throw new Exception("Token with id $id does not exist.");
}
/**
* Get Token count.
*/
public function getTokenCount(): int
{
return count($this->tokens);
}
/**
* Get Tokens.
*
* @return FormulaToken[]
*/
public function getTokens(): array
{
return $this->tokens;
}
/**
* Parse to tokens.
*/
private function parseToTokens(): void
{
// No attempt is made to verify formulas; assumes formulas are derived from Excel, where
// they can only exist if valid; stack overflows/underflows sunk as nulls without exceptions.
// Check if the formula has a valid starting =
$formulaLength = strlen($this->formula);
if ($formulaLength < 2 || $this->formula[0] != '=') {
return;
}
// Helper variables
$tokens1 = $tokens2 = $stack = [];
$inString = $inPath = $inRange = $inError = false;
$nextToken = null;
//$token = $previousToken = null;
$index = 1;
$value = '';
$ERRORS = ['#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!', '#N/A'];
$COMPARATORS_MULTI = ['>=', '<=', '<>'];
while ($index < $formulaLength) {
// state-dependent character evaluation (order is important)
// double-quoted strings
// embeds are doubled
// end marks token
if ($inString) {
if ($this->formula[$index] == self::QUOTE_DOUBLE) {
if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_DOUBLE)) {
$value .= self::QUOTE_DOUBLE;
++$index;
} else {
$inString = false;
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_TEXT);
$value = '';
}
} else {
$value .= $this->formula[$index];
}
++$index;
continue;
}
// single-quoted strings (links)
// embeds are double
// end does not mark a token
if ($inPath) {
if ($this->formula[$index] == self::QUOTE_SINGLE) {
if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_SINGLE)) {
$value .= self::QUOTE_SINGLE;
++$index;
} else {
$inPath = false;
}
} else {
$value .= $this->formula[$index];
}
++$index;
continue;
}
// bracked strings (R1C1 range index or linked workbook name)
// no embeds (changed to "()" by Excel)
// end does not mark a token
if ($inRange) {
if ($this->formula[$index] == self::BRACKET_CLOSE) {
$inRange = false;
}
$value .= $this->formula[$index];
++$index;
continue;
}
// error values
// end marks a token, determined from absolute list of values
if ($inError) {
$value .= $this->formula[$index];
++$index;
if (in_array($value, $ERRORS)) {
$inError = false;
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_ERROR);
$value = '';
}
continue;
}
// scientific notation check
if (str_contains(self::OPERATORS_SN, $this->formula[$index])) {
if (strlen($value) > 1) {
if (preg_match('/^[1-9]{1}(\\.\\d+)?E{1}$/', $this->formula[$index]) != 0) {
$value .= $this->formula[$index];
++$index;
continue;
}
}
}
// independent character evaluation (order not important)
// establish state-dependent character evaluations
if ($this->formula[$index] == self::QUOTE_DOUBLE) {
if ($value !== '') {
// unexpected
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
$value = '';
}
$inString = true;
++$index;
continue;
}
if ($this->formula[$index] == self::QUOTE_SINGLE) {
if ($value !== '') {
// unexpected
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
$value = '';
}
$inPath = true;
++$index;
continue;
}
if ($this->formula[$index] == self::BRACKET_OPEN) {
$inRange = true;
$value .= self::BRACKET_OPEN;
++$index;
continue;
}
if ($this->formula[$index] == self::ERROR_START) {
if ($value !== '') {
// unexpected
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
$value = '';
}
$inError = true;
$value .= self::ERROR_START;
++$index;
continue;
}
// mark start and end of arrays and array rows
if ($this->formula[$index] == self::BRACE_OPEN) {
if ($value !== '') {
// unexpected
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
$value = '';
}
$tmp = new FormulaToken('ARRAY', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
$tokens1[] = $tmp;
$stack[] = clone $tmp;
$tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
$tokens1[] = $tmp;
$stack[] = clone $tmp;
++$index;
continue;
}
if ($this->formula[$index] == self::SEMICOLON) {
if ($value !== '') {
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
$value = '';
}
/** @var FormulaToken $tmp */
$tmp = array_pop($stack);
$tmp->setValue('');
$tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
$tokens1[] = $tmp;
$tmp = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT);
$tokens1[] = $tmp;
$tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
$tokens1[] = $tmp;
$stack[] = clone $tmp;
++$index;
continue;
}
if ($this->formula[$index] == self::BRACE_CLOSE) {
if ($value !== '') {
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
$value = '';
}
/** @var FormulaToken $tmp */
$tmp = array_pop($stack);
$tmp->setValue('');
$tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
$tokens1[] = $tmp;
/** @var FormulaToken $tmp */
$tmp = array_pop($stack);
$tmp->setValue('');
$tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
$tokens1[] = $tmp;
++$index;
continue;
}
// trim white-space
if ($this->formula[$index] == self::WHITESPACE) {
if ($value !== '') {
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
$value = '';
}
$tokens1[] = new FormulaToken('', FormulaToken::TOKEN_TYPE_WHITESPACE);
++$index;
while (($this->formula[$index] == self::WHITESPACE) && ($index < $formulaLength)) {
++$index;
}
continue;
}
// multi-character comparators
if (($index + 2) <= $formulaLength) {
if (in_array(substr($this->formula, $index, 2), $COMPARATORS_MULTI)) {
if ($value !== '') {
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
$value = '';
}
$tokens1[] = new FormulaToken(substr($this->formula, $index, 2), FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_LOGICAL);
$index += 2;
continue;
}
}
// standard infix operators
if (str_contains(self::OPERATORS_INFIX, $this->formula[$index])) {
if ($value !== '') {
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
$value = '';
}
$tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORINFIX);
++$index;
continue;
}
// standard postfix operators (only one)
if (str_contains(self::OPERATORS_POSTFIX, $this->formula[$index])) {
if ($value !== '') {
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
$value = '';
}
$tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX);
++$index;
continue;
}
// start subexpression or function
if ($this->formula[$index] == self::PAREN_OPEN) {
if ($value !== '') {
$tmp = new FormulaToken($value, FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
$tokens1[] = $tmp;
$stack[] = clone $tmp;
$value = '';
} else {
$tmp = new FormulaToken('', FormulaToken::TOKEN_TYPE_SUBEXPRESSION, FormulaToken::TOKEN_SUBTYPE_START);
$tokens1[] = $tmp;
$stack[] = clone $tmp;
}
++$index;
continue;
}
// function, subexpression, or array parameters, or operand unions
if ($this->formula[$index] == self::COMMA) {
if ($value !== '') {
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
$value = '';
}
/** @var FormulaToken $tmp */
$tmp = array_pop($stack);
$tmp->setValue('');
$tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
$stack[] = $tmp;
if ($tmp->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) {
$tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_UNION);
} else {
$tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT);
}
++$index;
continue;
}
// stop subexpression
if ($this->formula[$index] == self::PAREN_CLOSE) {
if ($value !== '') {
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
$value = '';
}
/** @var FormulaToken $tmp */
$tmp = array_pop($stack);
$tmp->setValue('');
$tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
$tokens1[] = $tmp;
++$index;
continue;
}
// token accumulation
$value .= $this->formula[$index];
++$index;
}
// dump remaining accumulation
if ($value !== '') {
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
}
// move tokenList to new set, excluding unnecessary white-space tokens and converting necessary ones to intersections
$tokenCount = count($tokens1);
for ($i = 0; $i < $tokenCount; ++$i) {
$token = $tokens1[$i];
if (isset($tokens1[$i - 1])) {
$previousToken = $tokens1[$i - 1];
} else {
$previousToken = null;
}
if (isset($tokens1[$i + 1])) {
$nextToken = $tokens1[$i + 1];
} else {
$nextToken = null;
}
if ($token->getTokenType() != FormulaToken::TOKEN_TYPE_WHITESPACE) {
$tokens2[] = $token;
continue;
}
if ($previousToken === null) {
continue;
}
if (
!(
(($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
|| (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
|| ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
)
) {
continue;
}
if ($nextToken === null) {
continue;
}
if (
!(
(($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START))
|| (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START))
|| ($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
)
) {
continue;
}
$tokens2[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_INTERSECTION);
}
// move tokens to final list, switching infix "-" operators to prefix when appropriate, switching infix "+" operators
// to noop when appropriate, identifying operand and infix-operator subtypes, and pulling "@" from function names
$this->tokens = [];
$tokenCount = count($tokens2);
for ($i = 0; $i < $tokenCount; ++$i) {
$token = $tokens2[$i];
if (isset($tokens2[$i - 1])) {
$previousToken = $tokens2[$i - 1];
} else {
$previousToken = null;
}
if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '-') {
if ($i == 0) {
$token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX);
} elseif (
(($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION)
&& ($previousToken?->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
|| (($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION)
&& ($previousToken?->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
|| ($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX)
|| ($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
) {
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
} else {
$token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX);
}
$this->tokens[] = $token;
continue;
}
if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '+') {
if ($i == 0) {
continue;
} elseif (
(($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION)
&& ($previousToken?->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
|| (($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION)
&& ($previousToken?->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
|| ($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX)
|| ($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
) {
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
} else {
continue;
}
$this->tokens[] = $token;
continue;
}
if (
$token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX
&& $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING
) {
if (str_contains('<>=', substr($token->getValue(), 0, 1))) {
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL);
} elseif ($token->getValue() == '&') {
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_CONCATENATION);
} else {
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
}
$this->tokens[] = $token;
continue;
}
if (
$token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND
&& $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING
) {
if (!is_numeric($token->getValue())) {
if (strtoupper($token->getValue()) == 'TRUE' || strtoupper($token->getValue()) == 'FALSE') {
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL);
} else {
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_RANGE);
}
} else {
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_NUMBER);
}
$this->tokens[] = $token;
continue;
}
if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) {
if ($token->getValue() !== '') {
if (str_starts_with($token->getValue(), '@')) {
$token->setValue(substr($token->getValue(), 1));
}
}
}
$this->tokens[] = $token;
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMin.php 0000644 00000004356 15167673465 0020627 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Minimum;
class DMin extends DatabaseAbstract
{
/**
* DMIN.
*
* Returns the smallest number in a column of a list or database that matches conditions you that
* specify.
*
* Excel Function:
* DMIN(database,field,criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria, bool $returnError = true): float|string|null
{
$field = self::fieldExtract($database, $field);
if ($field === null) {
return $returnError ? ExcelError::VALUE() : null;
}
return Minimum::min(
self::getFilteredColumn($database, $field, $criteria)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVar.php 0000644 00000004457 15167673465 0020636 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Variances;
class DVar extends DatabaseAbstract
{
/**
* DVAR.
*
* Estimates the variance of a population based on a sample by using the numbers in a column
* of a list or database that match conditions that you specify.
*
* Excel Function:
* DVAR(database,field,criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*
* @return float|string (string if result is an error)
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria): string|float
{
$field = self::fieldExtract($database, $field);
if ($field === null) {
return ExcelError::VALUE();
}
return Variances::VAR(
self::getFilteredColumn($database, $field, $criteria)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DProduct.php 0000644 00000004273 15167673465 0021522 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
class DProduct extends DatabaseAbstract
{
/**
* DPRODUCT.
*
* Multiplies the values in a column of a list or database that match conditions that you specify.
*
* Excel Function:
* DPRODUCT(database,field,criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria): string|float
{
$field = self::fieldExtract($database, $field);
if ($field === null) {
return ExcelError::VALUE();
}
return MathTrig\Operations::product(
self::getFilteredColumn($database, $field, $criteria)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVarP.php 0000644 00000004501 15167673465 0020744 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Variances;
class DVarP extends DatabaseAbstract
{
/**
* DVARP.
*
* Calculates the variance of a population based on the entire population by using the numbers
* in a column of a list or database that match conditions that you specify.
*
* Excel Function:
* DVARP(database,field,criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*
* @return float|string (string if result is an error)
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria): string|float
{
$field = self::fieldExtract($database, $field);
if ($field === null) {
return ExcelError::VALUE();
}
return Variances::VARP(
self::getFilteredColumn($database, $field, $criteria)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCountA.php 0000644 00000004270 15167673465 0021270 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Counts;
class DCountA extends DatabaseAbstract
{
/**
* DCOUNTA.
*
* Counts the nonblank cells in a column of a list or database that match conditions that you specify.
*
* Excel Function:
* DCOUNTA(database,[field],criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria): string|int
{
$field = self::fieldExtract($database, $field);
if ($field === null) {
return ExcelError::VALUE();
}
return Counts::COUNTA(
self::getFilteredColumn($database, $field, $criteria)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDevP.php 0000644 00000004443 15167673465 0021246 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations;
class DStDevP extends DatabaseAbstract
{
/**
* DSTDEVP.
*
* Calculates the standard deviation of a population based on the entire population by using the
* numbers in a column of a list or database that match conditions that you specify.
*
* Excel Function:
* DSTDEVP(database,field,criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria): float|string
{
$field = self::fieldExtract($database, $field);
if ($field === null) {
return ExcelError::VALUE();
}
return StandardDeviations::STDEVP(
self::getFilteredColumn($database, $field, $criteria)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDev.php 0000644 00000004421 15167673465 0021122 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations;
class DStDev extends DatabaseAbstract
{
/**
* DSTDEV.
*
* Estimates the standard deviation of a population based on a sample by using the numbers in a
* column of a list or database that match conditions that you specify.
*
* Excel Function:
* DSTDEV(database,field,criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria): float|string
{
$field = self::fieldExtract($database, $field);
if ($field === null) {
return ExcelError::VALUE();
}
return StandardDeviations::STDEV(
self::getFilteredColumn($database, $field, $criteria)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php 0000644 00000004355 15167673465 0020630 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Maximum;
class DMax extends DatabaseAbstract
{
/**
* DMAX.
*
* Returns the largest number in a column of a list or database that matches conditions you that
* specify.
*
* Excel Function:
* DMAX(database,field,criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria, bool $returnError = true): null|float|string
{
$field = self::fieldExtract($database, $field);
if ($field === null) {
return $returnError ? ExcelError::VALUE() : null;
}
return Maximum::max(
self::getFilteredColumn($database, $field, $criteria)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCount.php 0000644 00000004361 15167673465 0021170 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Counts;
class DCount extends DatabaseAbstract
{
/**
* DCOUNT.
*
* Counts the cells that contain numbers in a column of a list or database that match conditions
* that you specify.
*
* Excel Function:
* DCOUNT(database,[field],criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria, bool $returnError = true): string|int
{
$field = self::fieldExtract($database, $field);
if ($returnError && $field === null) {
return ExcelError::VALUE();
}
return Counts::COUNT(
self::getFilteredColumn($database, $field, $criteria)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DAverage.php 0000644 00000004103 15167673465 0021444 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages;
class DAverage extends DatabaseAbstract
{
/**
* DAVERAGE.
*
* Averages the values in a column of a list or database that match conditions you specify.
*
* Excel Function:
* DAVERAGE(database,field,criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria): string|int|float
{
$field = self::fieldExtract($database, $field);
if ($field === null) {
return ExcelError::VALUE();
}
return Averages::average(
self::getFilteredColumn($database, $field, $criteria)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php 0000644 00000016566 15167673465 0023176 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
abstract class DatabaseAbstract
{
abstract public static function evaluate(array $database, array|null|int|string $field, array $criteria): null|float|int|string;
/**
* fieldExtract.
*
* Extracts the column ID to use for the data field.
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param mixed $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
*/
protected static function fieldExtract(array $database, mixed $field): ?int
{
$field = strtoupper(Functions::flattenSingleValue($field) ?? '');
if ($field === '') {
return null;
}
$fieldNames = array_map('strtoupper', array_shift($database));
if (is_numeric($field)) {
$field = (int) $field - 1;
if ($field < 0 || $field >= count($fieldNames)) {
return null;
}
return $field;
}
$key = array_search($field, array_values($fieldNames), true);
return ($key !== false) ? (int) $key : null;
}
/**
* filter.
*
* Parses the selection criteria, extracts the database rows that match those criteria, and
* returns that subset of rows.
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*
* @return mixed[]
*/
protected static function filter(array $database, array $criteria): array
{
$fieldNames = array_shift($database);
$criteriaNames = array_shift($criteria);
// Convert the criteria into a set of AND/OR conditions with [:placeholders]
$query = self::buildQuery($criteriaNames, $criteria);
// Loop through each row of the database
return self::executeQuery($database, $query, $criteriaNames, $fieldNames);
}
protected static function getFilteredColumn(array $database, ?int $field, array $criteria): array
{
// reduce the database to a set of rows that match all the criteria
$database = self::filter($database, $criteria);
$defaultReturnColumnValue = ($field === null) ? 1 : null;
// extract an array of values for the requested column
$columnData = [];
foreach ($database as $rowKey => $row) {
$keys = array_keys($row);
$key = $keys[$field] ?? null;
$columnKey = $key ?? 'A';
$columnData[$rowKey][$columnKey] = $row[$key] ?? $defaultReturnColumnValue;
}
return $columnData;
}
private static function buildQuery(array $criteriaNames, array $criteria): string
{
$baseQuery = [];
foreach ($criteria as $key => $criterion) {
foreach ($criterion as $field => $value) {
$criterionName = $criteriaNames[$field];
if ($value !== null) {
$condition = self::buildCondition($value, $criterionName);
$baseQuery[$key][] = $condition;
}
}
}
$rowQuery = array_map(
fn ($rowValue): string => (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : ($rowValue[0] ?? ''),
$baseQuery
);
return (count($rowQuery) > 1) ? 'OR(' . implode(',', $rowQuery) . ')' : ($rowQuery[0] ?? '');
}
private static function buildCondition(mixed $criterion, string $criterionName): string
{
$ifCondition = Functions::ifCondition($criterion);
// Check for wildcard characters used in the condition
$result = preg_match('/(?<operator>[^"]*)(?<operand>".*[*?].*")/ui', $ifCondition, $matches);
if ($result !== 1) {
return "[:{$criterionName}]{$ifCondition}";
}
$trueFalse = ($matches['operator'] !== '<>');
$wildcard = WildcardMatch::wildcard($matches['operand']);
$condition = "WILDCARDMATCH([:{$criterionName}],{$wildcard})";
if ($trueFalse === false) {
$condition = "NOT({$condition})";
}
return $condition;
}
private static function executeQuery(array $database, string $query, array $criteria, array $fields): array
{
foreach ($database as $dataRow => $dataValues) {
// Substitute actual values from the database row for our [:placeholders]
$conditions = $query;
foreach ($criteria as $criterion) {
$conditions = self::processCondition($criterion, $fields, $dataValues, $conditions);
}
// evaluate the criteria against the row data
$result = Calculation::getInstance()->_calculateFormulaValue('=' . $conditions);
// If the row failed to meet the criteria, remove it from the database
if ($result !== true) {
unset($database[$dataRow]);
}
}
return $database;
}
private static function processCondition(string $criterion, array $fields, array $dataValues, string $conditions): string
{
$key = array_search($criterion, $fields, true);
$dataValue = 'NULL';
if (is_bool($dataValues[$key])) {
$dataValue = ($dataValues[$key]) ? 'TRUE' : 'FALSE';
} elseif ($dataValues[$key] !== null) {
$dataValue = $dataValues[$key];
// escape quotes if we have a string containing quotes
if (is_string($dataValue) && str_contains($dataValue, '"')) {
$dataValue = str_replace('"', '""', $dataValue);
}
$dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue;
}
return str_replace('[:' . $criterion . ']', $dataValue, $conditions);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DGet.php 0000644 00000004404 15167673465 0020615 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class DGet extends DatabaseAbstract
{
/**
* DGET.
*
* Extracts a single value from a column of a list or database that matches conditions that you
* specify.
*
* Excel Function:
* DGET(database,field,criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria): null|float|int|string
{
$field = self::fieldExtract($database, $field);
if ($field === null) {
return ExcelError::VALUE();
}
$columnData = self::getFilteredColumn($database, $field, $criteria);
if (count($columnData) > 1) {
return ExcelError::NAN();
}
$row = array_pop($columnData);
return array_pop($row);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DSum.php 0000644 00000004342 15167673465 0020643 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
class DSum extends DatabaseAbstract
{
/**
* DSUM.
*
* Adds the numbers in a column of a list or database that match conditions that you specify.
*
* Excel Function:
* DSUM(database,field,criteria)
*
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*/
public static function evaluate(array $database, array|null|int|string $field, array $criteria, bool $returnNull = false): null|float|string
{
$field = self::fieldExtract($database, $field);
if ($field === null) {
return $returnNull ? null : ExcelError::VALUE();
}
return MathTrig\Sum::sumIgnoringStrings(
self::getFilteredColumn($database, $field, $criteria)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Boolean.php 0000644 00000001035 15167673465 0021214 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Logical;
class Boolean
{
/**
* TRUE.
*
* Returns the boolean TRUE.
*
* Excel Function:
* =TRUE()
*
* @return bool True
*/
public static function true(): bool
{
return true;
}
/**
* FALSE.
*
* Returns the boolean FALSE.
*
* Excel Function:
* =FALSE()
*
* @return bool False
*/
public static function false(): bool
{
return false;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Conditional.php 0000644 00000021613 15167673465 0022104 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Information\Value;
class Conditional
{
use ArrayEnabled;
/**
* STATEMENT_IF.
*
* Returns one value if a condition you specify evaluates to TRUE and another value if it evaluates to FALSE.
*
* Excel Function:
* =IF(condition[,returnIfTrue[,returnIfFalse]])
*
* Condition is any value or expression that can be evaluated to TRUE or FALSE.
* For example, A10=100 is a logical expression; if the value in cell A10 is equal to 100,
* the expression evaluates to TRUE. Otherwise, the expression evaluates to FALSE.
* This argument can use any comparison calculation operator.
* ReturnIfTrue is the value that is returned if condition evaluates to TRUE.
* For example, if this argument is the text string "Within budget" and
* the condition argument evaluates to TRUE, then the IF function returns the text "Within budget"
* If condition is TRUE and ReturnIfTrue is blank, this argument returns 0 (zero).
* To display the word TRUE, use the logical value TRUE for this argument.
* ReturnIfTrue can be another formula.
* ReturnIfFalse is the value that is returned if condition evaluates to FALSE.
* For example, if this argument is the text string "Over budget" and the condition argument evaluates
* to FALSE, then the IF function returns the text "Over budget".
* If condition is FALSE and ReturnIfFalse is omitted, then the logical value FALSE is returned.
* If condition is FALSE and ReturnIfFalse is blank, then the value 0 (zero) is returned.
* ReturnIfFalse can be another formula.
*
* @param mixed $condition Condition to evaluate
* @param mixed $returnIfTrue Value to return when condition is true
* Note that this can be an array value
* @param mixed $returnIfFalse Optional value to return when condition is false
* Note that this can be an array value
*
* @return mixed The value of returnIfTrue or returnIfFalse determined by condition
*/
public static function statementIf(mixed $condition = true, mixed $returnIfTrue = 0, mixed $returnIfFalse = false): mixed
{
$condition = ($condition === null) ? true : Functions::flattenSingleValue($condition);
if (ErrorValue::isError($condition)) {
return $condition;
}
$returnIfTrue = $returnIfTrue ?? 0;
$returnIfFalse = $returnIfFalse ?? false;
return ((bool) $condition) ? $returnIfTrue : $returnIfFalse;
}
/**
* STATEMENT_SWITCH.
*
* Returns corresponding with first match (any data type such as a string, numeric, date, etc).
*
* Excel Function:
* =SWITCH (expression, value1, result1, value2, result2, ... value_n, result_n [, default])
*
* Expression
* The expression to compare to a list of values.
* value1, value2, ... value_n
* A list of values that are compared to expression.
* The SWITCH function is looking for the first value that matches the expression.
* result1, result2, ... result_n
* A list of results. The SWITCH function returns the corresponding result when a value
* matches expression.
* Note that these can be array values to be returned
* default
* Optional. It is the default to return if expression does not match any of the values
* (value1, value2, ... value_n).
* Note that this can be an array value to be returned
*
* @param mixed $arguments Statement arguments
*
* @return mixed The value of matched expression
*/
public static function statementSwitch(mixed ...$arguments): mixed
{
$result = ExcelError::VALUE();
if (count($arguments) > 0) {
$targetValue = Functions::flattenSingleValue($arguments[0]);
$argc = count($arguments) - 1;
$switchCount = floor($argc / 2);
$hasDefaultClause = $argc % 2 !== 0;
$defaultClause = $argc % 2 === 0 ? null : $arguments[$argc];
$switchSatisfied = false;
if ($switchCount > 0) {
for ($index = 0; $index < $switchCount; ++$index) {
if ($targetValue == Functions::flattenSingleValue($arguments[$index * 2 + 1])) {
$result = $arguments[$index * 2 + 2];
$switchSatisfied = true;
break;
}
}
}
if ($switchSatisfied !== true) {
$result = $hasDefaultClause ? $defaultClause : ExcelError::NA();
}
}
return $result;
}
/**
* IFERROR.
*
* Excel Function:
* =IFERROR(testValue,errorpart)
*
* @param mixed $testValue Value to check, is also the value returned when no error
* Or can be an array of values
* @param mixed $errorpart Value to return when testValue is an error condition
* Note that this can be an array value to be returned
*
* @return mixed The value of errorpart or testValue determined by error condition
* If an array of values is passed as the $testValue argument, then the returned result will also be
* an array with the same dimensions
*/
public static function IFERROR(mixed $testValue = '', mixed $errorpart = ''): mixed
{
if (is_array($testValue)) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $testValue, $errorpart);
}
$errorpart = $errorpart ?? '';
$testValue = $testValue ?? 0; // this is how Excel handles empty cell
return self::statementIf(ErrorValue::isError($testValue), $errorpart, $testValue);
}
/**
* IFNA.
*
* Excel Function:
* =IFNA(testValue,napart)
*
* @param mixed $testValue Value to check, is also the value returned when not an NA
* Or can be an array of values
* @param mixed $napart Value to return when testValue is an NA condition
* Note that this can be an array value to be returned
*
* @return mixed The value of errorpart or testValue determined by error condition
* If an array of values is passed as the $testValue argument, then the returned result will also be
* an array with the same dimensions
*/
public static function IFNA(mixed $testValue = '', mixed $napart = ''): mixed
{
if (is_array($testValue)) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $testValue, $napart);
}
$napart = $napart ?? '';
$testValue = $testValue ?? 0; // this is how Excel handles empty cell
return self::statementIf(ErrorValue::isNa($testValue), $napart, $testValue);
}
/**
* IFS.
*
* Excel Function:
* =IFS(testValue1;returnIfTrue1;testValue2;returnIfTrue2;...;testValue_n;returnIfTrue_n)
*
* testValue1 ... testValue_n
* Conditions to Evaluate
* returnIfTrue1 ... returnIfTrue_n
* Value returned if corresponding testValue (nth) was true
*
* @param mixed ...$arguments Statement arguments
* Note that this can be an array value to be returned
*
* @return mixed|string The value of returnIfTrue_n, if testValue_n was true. #N/A if none of testValues was true
*/
public static function IFS(mixed ...$arguments)
{
$argumentCount = count($arguments);
if ($argumentCount % 2 != 0) {
return ExcelError::NA();
}
// We use instance of Exception as a falseValue in order to prevent string collision with value in cell
$falseValueException = new Exception();
for ($i = 0; $i < $argumentCount; $i += 2) {
$testValue = ($arguments[$i] === null) ? '' : Functions::flattenSingleValue($arguments[$i]);
$returnIfTrue = ($arguments[$i + 1] === null) ? '' : $arguments[$i + 1];
$result = self::statementIf($testValue, $returnIfTrue, $falseValueException);
if ($result !== $falseValueException) {
return $result;
}
}
return ExcelError::NA();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Operations.php 0000644 00000014713 15167673465 0021767 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Operations
{
use ArrayEnabled;
/**
* LOGICAL_AND.
*
* Returns boolean TRUE if all its arguments are TRUE; returns FALSE if one or more argument is FALSE.
*
* Excel Function:
* =AND(logical1[,logical2[, ...]])
*
* The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
* or references that contain logical values.
*
* Boolean arguments are treated as True or False as appropriate
* Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
* If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string
* holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
*
* @param mixed ...$args Data values
*
* @return bool|string the logical AND of the arguments
*/
public static function logicalAnd(mixed ...$args)
{
return self::countTrueValues($args, fn (int $trueValueCount, int $count): bool => $trueValueCount === $count);
}
/**
* LOGICAL_OR.
*
* Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE.
*
* Excel Function:
* =OR(logical1[,logical2[, ...]])
*
* The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
* or references that contain logical values.
*
* Boolean arguments are treated as True or False as appropriate
* Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
* If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string
* holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
*
* @param mixed $args Data values
*
* @return bool|string the logical OR of the arguments
*/
public static function logicalOr(mixed ...$args)
{
return self::countTrueValues($args, fn (int $trueValueCount): bool => $trueValueCount > 0);
}
/**
* LOGICAL_XOR.
*
* Returns the Exclusive Or logical operation for one or more supplied conditions.
* i.e. the Xor function returns TRUE if an odd number of the supplied conditions evaluate to TRUE,
* and FALSE otherwise.
*
* Excel Function:
* =XOR(logical1[,logical2[, ...]])
*
* The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
* or references that contain logical values.
*
* Boolean arguments are treated as True or False as appropriate
* Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
* If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string
* holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
*
* @param mixed $args Data values
*
* @return bool|string the logical XOR of the arguments
*/
public static function logicalXor(mixed ...$args)
{
return self::countTrueValues($args, fn (int $trueValueCount): bool => $trueValueCount % 2 === 1);
}
/**
* NOT.
*
* Returns the boolean inverse of the argument.
*
* Excel Function:
* =NOT(logical)
*
* The argument must evaluate to a logical value such as TRUE or FALSE
*
* Boolean arguments are treated as True or False as appropriate
* Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
* If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string
* holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
*
* @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE
* Or can be an array of values
*
* @return array|bool|string the boolean inverse of the argument
* If an array of values is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function NOT(mixed $logical = false): array|bool|string
{
if (is_array($logical)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $logical);
}
if (is_string($logical)) {
$logical = mb_strtoupper($logical, 'UTF-8');
if (($logical == 'TRUE') || ($logical == Calculation::getTRUE())) {
return false;
} elseif (($logical == 'FALSE') || ($logical == Calculation::getFALSE())) {
return true;
}
return ExcelError::VALUE();
}
return !$logical;
}
private static function countTrueValues(array $args, callable $func): bool|string
{
$trueValueCount = 0;
$count = 0;
$aArgs = Functions::flattenArrayIndexed($args);
foreach ($aArgs as $k => $arg) {
++$count;
// Is it a boolean value?
if (is_bool($arg)) {
$trueValueCount += $arg;
} elseif (is_string($arg)) {
$isLiteral = !Functions::isCellValue($k);
$arg = mb_strtoupper($arg, 'UTF-8');
if ($isLiteral && ($arg == 'TRUE' || $arg == Calculation::getTRUE())) {
++$trueValueCount;
} elseif ($isLiteral && ($arg == 'FALSE' || $arg == Calculation::getFALSE())) {
//$trueValueCount += 0;
} else {
--$count;
}
} elseif (is_int($arg) || is_float($arg)) {
$trueValueCount += (int) ($arg != 0);
} else {
--$count;
}
}
return ($count === 0) ? ExcelError::VALUE() : $func($trueValueCount, $count);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php 0000644 00000000702 15167673465 0021517 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
class ExceptionHandler
{
/**
* Register errorhandler.
*/
public function __construct()
{
/** @var callable $callable */
$callable = [Exception::class, 'errorHandlerCallback'];
set_error_handler($callable, E_ALL);
}
/**
* Unregister errorhandler.
*/
public function __destruct()
{
restore_error_handler();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php 0000644 00000024251 15167673465 0020240 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Shared\Date;
class Functions
{
const PRECISION = 8.88E-016;
/**
* 2 / PI.
*/
const M_2DIVPI = 0.63661977236758134307553505349006;
const COMPATIBILITY_EXCEL = 'Excel';
const COMPATIBILITY_GNUMERIC = 'Gnumeric';
const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc';
/** Use of RETURNDATE_PHP_NUMERIC is discouraged - not 32-bit Y2038-safe, no timezone. */
const RETURNDATE_PHP_NUMERIC = 'P';
/** Use of RETURNDATE_UNIX_TIMESTAMP is discouraged - not 32-bit Y2038-safe, no timezone. */
const RETURNDATE_UNIX_TIMESTAMP = 'P';
const RETURNDATE_PHP_OBJECT = 'O';
const RETURNDATE_PHP_DATETIME_OBJECT = 'O';
const RETURNDATE_EXCEL = 'E';
/**
* Compatibility mode to use for error checking and responses.
*/
protected static string $compatibilityMode = self::COMPATIBILITY_EXCEL;
/**
* Data Type to use when returning date values.
*/
protected static string $returnDateType = self::RETURNDATE_EXCEL;
/**
* Set the Compatibility Mode.
*
* @param string $compatibilityMode Compatibility Mode
* Permitted values are:
* Functions::COMPATIBILITY_EXCEL 'Excel'
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
*
* @return bool (Success or Failure)
*/
public static function setCompatibilityMode(string $compatibilityMode): bool
{
if (
($compatibilityMode == self::COMPATIBILITY_EXCEL)
|| ($compatibilityMode == self::COMPATIBILITY_GNUMERIC)
|| ($compatibilityMode == self::COMPATIBILITY_OPENOFFICE)
) {
self::$compatibilityMode = $compatibilityMode;
return true;
}
return false;
}
/**
* Return the current Compatibility Mode.
*
* @return string Compatibility Mode
* Possible Return values are:
* Functions::COMPATIBILITY_EXCEL 'Excel'
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
*/
public static function getCompatibilityMode(): string
{
return self::$compatibilityMode;
}
/**
* Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP DateTime Object).
*
* @param string $returnDateType Return Date Format
* Permitted values are:
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
* Functions::RETURNDATE_EXCEL 'E'
*
* @return bool Success or failure
*/
public static function setReturnDateType(string $returnDateType): bool
{
if (
($returnDateType == self::RETURNDATE_UNIX_TIMESTAMP)
|| ($returnDateType == self::RETURNDATE_PHP_DATETIME_OBJECT)
|| ($returnDateType == self::RETURNDATE_EXCEL)
) {
self::$returnDateType = $returnDateType;
return true;
}
return false;
}
/**
* Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object).
*
* @return string Return Date Format
* Possible Return values are:
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
* Functions::RETURNDATE_EXCEL ' 'E'
*/
public static function getReturnDateType(): string
{
return self::$returnDateType;
}
/**
* DUMMY.
*
* @return string #Not Yet Implemented
*/
public static function DUMMY(): string
{
return '#Not Yet Implemented';
}
public static function isMatrixValue(mixed $idx): bool
{
return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0);
}
public static function isValue(mixed $idx): bool
{
return substr_count($idx, '.') === 0;
}
public static function isCellValue(mixed $idx): bool
{
return substr_count($idx, '.') > 1;
}
public static function ifCondition(mixed $condition): string
{
$condition = self::flattenSingleValue($condition);
if ($condition === '' || $condition === null) {
return '=""';
}
if (!is_string($condition) || !in_array($condition[0], ['>', '<', '='], true)) {
$condition = self::operandSpecialHandling($condition);
if (is_bool($condition)) {
return '=' . ($condition ? 'TRUE' : 'FALSE');
} elseif (!is_numeric($condition)) {
if ($condition !== '""') { // Not an empty string
// Escape any quotes in the string value
$condition = (string) preg_replace('/"/ui', '""', $condition);
}
$condition = Calculation::wrapResult(strtoupper($condition));
}
return str_replace('""""', '""', '=' . $condition);
}
preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches);
[, $operator, $operand] = $matches;
$operand = self::operandSpecialHandling($operand);
if (is_numeric(trim($operand, '"'))) {
$operand = trim($operand, '"');
} elseif (!is_numeric($operand) && $operand !== 'FALSE' && $operand !== 'TRUE') {
$operand = str_replace('"', '""', $operand);
$operand = Calculation::wrapResult(strtoupper($operand));
}
return str_replace('""""', '""', $operator . $operand);
}
private static function operandSpecialHandling(mixed $operand): mixed
{
if (is_numeric($operand) || is_bool($operand)) {
return $operand;
} elseif (strtoupper($operand) === Calculation::getTRUE() || strtoupper($operand) === Calculation::getFALSE()) {
return strtoupper($operand);
}
// Check for percentage
if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $operand)) {
return ((float) rtrim($operand, '%')) / 100;
}
// Check for dates
if (($dateValueOperand = Date::stringToExcel($operand)) !== false) {
return $dateValueOperand;
}
return $operand;
}
/**
* Convert a multi-dimensional array to a simple 1-dimensional array.
*
* @param mixed $array Array to be flattened
*
* @return array Flattened array
*/
public static function flattenArray(mixed $array): array
{
if (!is_array($array)) {
return (array) $array;
}
$flattened = [];
$stack = array_values($array);
while (!empty($stack)) {
$value = array_shift($stack);
if (is_array($value)) {
array_unshift($stack, ...array_values($value));
} else {
$flattened[] = $value;
}
}
return $flattened;
}
public static function scalar(mixed $value): mixed
{
if (!is_array($value)) {
return $value;
}
do {
$value = array_pop($value);
} while (is_array($value));
return $value;
}
/**
* Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing.
*
* @param array|mixed $array Array to be flattened
*
* @return array Flattened array
*/
public static function flattenArrayIndexed($array): array
{
if (!is_array($array)) {
return (array) $array;
}
$arrayValues = [];
foreach ($array as $k1 => $value) {
if (is_array($value)) {
foreach ($value as $k2 => $val) {
if (is_array($val)) {
foreach ($val as $k3 => $v) {
$arrayValues[$k1 . '.' . $k2 . '.' . $k3] = $v;
}
} else {
$arrayValues[$k1 . '.' . $k2] = $val;
}
}
} else {
$arrayValues[$k1] = $value;
}
}
return $arrayValues;
}
/**
* Convert an array to a single scalar value by extracting the first element.
*
* @param mixed $value Array or scalar value
*/
public static function flattenSingleValue(mixed $value): mixed
{
while (is_array($value)) {
$value = array_shift($value);
}
return $value;
}
public static function expandDefinedName(string $coordinate, Cell $cell): string
{
$worksheet = $cell->getWorksheet();
$spreadsheet = $worksheet->getParentOrThrow();
// Uppercase coordinate
$pCoordinatex = strtoupper($coordinate);
// Eliminate leading equal sign
$pCoordinatex = (string) preg_replace('/^=/', '', $pCoordinatex);
$defined = $spreadsheet->getDefinedName($pCoordinatex, $worksheet);
if ($defined !== null) {
$worksheet2 = $defined->getWorkSheet();
if (!$defined->isFormula() && $worksheet2 !== null) {
$coordinate = "'" . $worksheet2->getTitle() . "'!"
. (string) preg_replace('/^=/', '', str_replace('$', '', $defined->getValue()));
}
}
return $coordinate;
}
public static function trimTrailingRange(string $coordinate): string
{
return (string) preg_replace('/:[\\w\$]+$/', '', $coordinate);
}
public static function trimSheetFromCellReference(string $coordinate): string
{
if (str_contains($coordinate, '!')) {
$coordinate = substr($coordinate, strrpos($coordinate, '!') + 1);
}
return $coordinate;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Constants.php 0000644 00000000247 15167673465 0022475 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
class Constants
{
/**
* EULER.
*/
public const EULER = 2.71828182845904523536;
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php 0000644 00000013403 15167673465 0022046 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselJ
{
use ArrayEnabled;
/**
* BESSELJ.
*
* Returns the Bessel function
*
* Excel Function:
* BESSELJ(x,ord)
*
* NOTE: The MS Excel implementation of the BESSELJ function is still not accurate, particularly for higher order
* values with x < -8 and x > 8. This code provides a more accurate calculation
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELJ returns the #VALUE! error value.
* Or can be an array of values
* @param mixed $ord The integer order of the Bessel function.
* If ord is not an integer, it is truncated.
* If $ord is nonnumeric, BESSELJ returns the #VALUE! error value.
* If $ord < 0, BESSELJ returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELJ(mixed $x, mixed $ord): array|string|float
{
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
$ord = EngineeringValidations::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if ($ord < 0) {
return ExcelError::NAN();
}
$fResult = self::calculate($x, $ord);
return (is_nan($fResult)) ? ExcelError::NAN() : $fResult;
}
private static function calculate(float $x, int $ord): float
{
return match ($ord) {
0 => self::besselJ0($x),
1 => self::besselJ1($x),
default => self::besselJ2($x, $ord),
};
}
private static function besselJ0(float $x): float
{
$ax = abs($x);
if ($ax < 8.0) {
$y = $x * $x;
$ans1 = 57568490574.0 + $y * (-13362590354.0 + $y * (651619640.7 + $y * (-11214424.18 + $y
* (77392.33017 + $y * (-184.9052456)))));
$ans2 = 57568490411.0 + $y * (1029532985.0 + $y * (9494680.718 + $y * (59272.64853 + $y
* (267.8532712 + $y * 1.0))));
return $ans1 / $ans2;
}
$z = 8.0 / $ax;
$y = $z * $z;
$xx = $ax - 0.785398164;
$ans1 = 1.0 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
$ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y
* (0.7621095161e-6 - $y * 0.934935152e-7)));
return sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2);
}
private static function besselJ1(float $x): float
{
$ax = abs($x);
if ($ax < 8.0) {
$y = $x * $x;
$ans1 = $x * (72362614232.0 + $y * (-7895059235.0 + $y * (242396853.1 + $y
* (-2972611.439 + $y * (15704.48260 + $y * (-30.16036606))))));
$ans2 = 144725228442.0 + $y * (2300535178.0 + $y * (18583304.74 + $y * (99447.43394 + $y
* (376.9991397 + $y * 1.0))));
return $ans1 / $ans2;
}
$z = 8.0 / $ax;
$y = $z * $z;
$xx = $ax - 2.356194491;
$ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6))));
$ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y
* (-0.88228987e-6 + $y * 0.105787412e-6)));
$ans = sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2);
return ($x < 0.0) ? -$ans : $ans;
}
private static function besselJ2(float $x, int $ord): float
{
$ax = abs($x);
if ($ax === 0.0) {
return 0.0;
}
if ($ax > $ord) {
return self::besselj2a($ax, $ord, $x);
}
return self::besselj2b($ax, $ord, $x);
}
private static function besselj2a(float $ax, int $ord, float $x): float
{
$tox = 2.0 / $ax;
$bjm = self::besselJ0($ax);
$bj = self::besselJ1($ax);
for ($j = 1; $j < $ord; ++$j) {
$bjp = $j * $tox * $bj - $bjm;
$bjm = $bj;
$bj = $bjp;
}
$ans = $bj;
return ($x < 0.0 && ($ord % 2) == 1) ? -$ans : $ans;
}
private static function besselj2b(float $ax, int $ord, float $x): float
{
$tox = 2.0 / $ax;
$jsum = false;
$bjp = $ans = $sum = 0.0;
$bj = 1.0;
for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) {
$bjm = $j * $tox * $bj - $bjp;
$bjp = $bj;
$bj = $bjm;
if (abs($bj) > 1.0e+10) {
$bj *= 1.0e-10;
$bjp *= 1.0e-10;
$ans *= 1.0e-10;
$sum *= 1.0e-10;
}
if ($jsum === true) {
$sum += $bj;
}
$jsum = $jsum === false;
if ($j === $ord) {
$ans = $bjp;
}
}
$sum = 2.0 * $sum - $bj;
$ans /= $sum;
return ($x < 0.0 && ($ord % 2) === 1) ? -$ans : $ans;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php 0000644 00000006110 15167673465 0022102 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Compare
{
use ArrayEnabled;
/**
* DELTA.
*
* Excel Function:
* DELTA(a[,b])
*
* Tests whether two values are equal. Returns 1 if number1 = number2; returns 0 otherwise.
* Use this function to filter a set of values. For example, by summing several DELTA
* functions you calculate the count of equal pairs. This function is also known as the
* Kronecker Delta function.
*
* @param array|bool|float|int|string $a the first number
* Or can be an array of values
* @param array|bool|float|int|string $b The second number. If omitted, b is assumed to be zero.
* Or can be an array of values
*
* @return array|int|string (string in the event of an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function DELTA(array|float|bool|string|int $a, array|float|bool|string|int $b = 0.0): array|string|int
{
if (is_array($a) || is_array($b)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $a, $b);
}
try {
$a = EngineeringValidations::validateFloat($a);
$b = EngineeringValidations::validateFloat($b);
} catch (Exception $e) {
return $e->getMessage();
}
return (int) (abs($a - $b) < 1.0e-15);
}
/**
* GESTEP.
*
* Excel Function:
* GESTEP(number[,step])
*
* Returns 1 if number >= step; returns 0 (zero) otherwise
* Use this function to filter a set of values. For example, by summing several GESTEP
* functions you calculate the count of values that exceed a threshold.
*
* @param array|bool|float|int|string $number the value to test against step
* Or can be an array of values
* @param null|array|bool|float|int|string $step The threshold value. If you omit a value for step, GESTEP uses zero.
* Or can be an array of values
*
* @return array|int|string (string in the event of an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function GESTEP(array|float|bool|string|int $number, $step = 0.0): array|string|int
{
if (is_array($number) || is_array($step)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $step);
}
try {
$number = EngineeringValidations::validateFloat($number);
$step = EngineeringValidations::validateFloat($step ?? 0.0);
} catch (Exception $e) {
return $e->getMessage();
}
return (int) ($number >= $step);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php 0000644 00000022574 15167673465 0023427 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertDecimal extends ConvertBase
{
const LARGEST_OCTAL_IN_DECIMAL = 536870911;
const SMALLEST_OCTAL_IN_DECIMAL = -536870912;
const LARGEST_BINARY_IN_DECIMAL = 511;
const SMALLEST_BINARY_IN_DECIMAL = -512;
const LARGEST_HEX_IN_DECIMAL = 549755813887;
const SMALLEST_HEX_IN_DECIMAL = -549755813888;
/**
* toBinary.
*
* Return a decimal value as binary.
*
* Excel Function:
* DEC2BIN(x[,places])
*
* @param array|bool|float|int|string $value The decimal integer you want to convert. If number is negative,
* valid place values are ignored and DEC2BIN returns a 10-character
* (10-bit) binary number in which the most significant bit is the sign
* bit. The remaining 9 bits are magnitude bits. Negative numbers are
* represented using two's-complement notation.
* If number < -512 or if number > 511, DEC2BIN returns the #NUM! error
* value.
* If number is nonnumeric, DEC2BIN returns the #VALUE! error value.
* If DEC2BIN requires more than places characters, it returns the #NUM!
* error value.
* Or can be an array of values
* @param null|array|float|int|string $places The number of characters to use. If places is omitted, DEC2BIN uses
* the minimum number of characters necessary. Places is useful for
* padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, DEC2BIN returns the #VALUE! error value.
* If places is zero or negative, DEC2BIN returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toBinary($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateDecimal($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$value = (int) floor((float) $value);
if ($value > self::LARGEST_BINARY_IN_DECIMAL || $value < self::SMALLEST_BINARY_IN_DECIMAL) {
return ExcelError::NAN();
}
$r = decbin($value);
// Two's Complement
$r = substr($r, -10);
return self::nbrConversionFormat($r, $places);
}
/**
* toHex.
*
* Return a decimal value as hex.
*
* Excel Function:
* DEC2HEX(x[,places])
*
* @param array|bool|float|int|string $value The decimal integer you want to convert. If number is negative,
* places is ignored and DEC2HEX returns a 10-character (40-bit)
* hexadecimal number in which the most significant bit is the sign
* bit. The remaining 39 bits are magnitude bits. Negative numbers
* are represented using two's-complement notation.
* If number < -549,755,813,888 or if number > 549,755,813,887,
* DEC2HEX returns the #NUM! error value.
* If number is nonnumeric, DEC2HEX returns the #VALUE! error value.
* If DEC2HEX requires more than places characters, it returns the
* #NUM! error value.
* Or can be an array of values
* @param null|array|float|int|string $places The number of characters to use. If places is omitted, DEC2HEX uses
* the minimum number of characters necessary. Places is useful for
* padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, DEC2HEX returns the #VALUE! error value.
* If places is zero or negative, DEC2HEX returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toHex($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateDecimal($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$value = floor((float) $value);
if ($value > self::LARGEST_HEX_IN_DECIMAL || $value < self::SMALLEST_HEX_IN_DECIMAL) {
return ExcelError::NAN();
}
$r = strtoupper(dechex((int) $value));
$r = self::hex32bit($value, $r);
return self::nbrConversionFormat($r, $places);
}
public static function hex32bit(float $value, string $hexstr, bool $force = false): string
{
if (PHP_INT_SIZE === 4 || $force) {
if ($value >= 2 ** 32) {
$quotient = (int) ($value / (2 ** 32));
return strtoupper(substr('0' . dechex($quotient), -2) . $hexstr);
}
if ($value < -(2 ** 32)) {
$quotient = 256 - (int) ceil((-$value) / (2 ** 32));
return strtoupper(substr('0' . dechex($quotient), -2) . substr("00000000$hexstr", -8));
}
if ($value < 0) {
return "FF$hexstr";
}
}
return $hexstr;
}
/**
* toOctal.
*
* Return an decimal value as octal.
*
* Excel Function:
* DEC2OCT(x[,places])
*
* @param array|bool|float|int|string $value The decimal integer you want to convert. If number is negative,
* places is ignored and DEC2OCT returns a 10-character (30-bit)
* octal number in which the most significant bit is the sign bit.
* The remaining 29 bits are magnitude bits. Negative numbers are
* represented using two's-complement notation.
* If number < -536,870,912 or if number > 536,870,911, DEC2OCT
* returns the #NUM! error value.
* If number is nonnumeric, DEC2OCT returns the #VALUE! error value.
* If DEC2OCT requires more than places characters, it returns the
* #NUM! error value.
* Or can be an array of values
* @param array|int $places The number of characters to use. If places is omitted, DEC2OCT uses
* the minimum number of characters necessary. Places is useful for
* padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, DEC2OCT returns the #VALUE! error value.
* If places is zero or negative, DEC2OCT returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toOctal($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateDecimal($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$value = (int) floor((float) $value);
if ($value > self::LARGEST_OCTAL_IN_DECIMAL || $value < self::SMALLEST_OCTAL_IN_DECIMAL) {
return ExcelError::NAN();
}
$r = decoct($value);
$r = substr($r, -10);
return self::nbrConversionFormat($r, $places);
}
protected static function validateDecimal(string $value): string
{
if (strlen($value) > preg_match_all('/[-0123456789.]/', $value)) {
throw new Exception(ExcelError::VALUE());
}
return $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php 0000644 00000011005 15167673465 0022041 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselI
{
use ArrayEnabled;
/**
* BESSELI.
*
* Returns the modified Bessel function In(x), which is equivalent to the Bessel function evaluated
* for purely imaginary arguments
*
* Excel Function:
* BESSELI(x,ord)
*
* NOTE: The MS Excel implementation of the BESSELI function is still not accurate.
* This code provides a more accurate calculation
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELI returns the #VALUE! error value.
* Or can be an array of values
* @param mixed $ord The integer order of the Bessel function.
* If ord is not an integer, it is truncated.
* If $ord is nonnumeric, BESSELI returns the #VALUE! error value.
* If $ord < 0, BESSELI returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELI(mixed $x, mixed $ord): array|string|float
{
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
$ord = EngineeringValidations::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if ($ord < 0) {
return ExcelError::NAN();
}
$fResult = self::calculate($x, $ord);
return (is_nan($fResult)) ? ExcelError::NAN() : $fResult;
}
private static function calculate(float $x, int $ord): float
{
return match ($ord) {
0 => self::besselI0($x),
1 => self::besselI1($x),
default => self::besselI2($x, $ord),
};
}
private static function besselI0(float $x): float
{
$ax = abs($x);
if ($ax < 3.75) {
$y = $x / 3.75;
$y = $y * $y;
return 1.0 + $y * (3.5156229 + $y * (3.0899424 + $y * (1.2067492
+ $y * (0.2659732 + $y * (0.360768e-1 + $y * 0.45813e-2)))));
}
$y = 3.75 / $ax;
return (exp($ax) / sqrt($ax)) * (0.39894228 + $y * (0.1328592e-1 + $y * (0.225319e-2 + $y * (-0.157565e-2
+ $y * (0.916281e-2 + $y * (-0.2057706e-1 + $y * (0.2635537e-1
+ $y * (-0.1647633e-1 + $y * 0.392377e-2))))))));
}
private static function besselI1(float $x): float
{
$ax = abs($x);
if ($ax < 3.75) {
$y = $x / 3.75;
$y = $y * $y;
$ans = $ax * (0.5 + $y * (0.87890594 + $y * (0.51498869 + $y * (0.15084934 + $y * (0.2658733e-1
+ $y * (0.301532e-2 + $y * 0.32411e-3))))));
return ($x < 0.0) ? -$ans : $ans;
}
$y = 3.75 / $ax;
$ans = 0.2282967e-1 + $y * (-0.2895312e-1 + $y * (0.1787654e-1 - $y * 0.420059e-2));
$ans = 0.39894228 + $y * (-0.3988024e-1 + $y * (-0.362018e-2 + $y * (0.163801e-2
+ $y * (-0.1031555e-1 + $y * $ans))));
$ans *= exp($ax) / sqrt($ax);
return ($x < 0.0) ? -$ans : $ans;
}
private static function besselI2(float $x, int $ord): float
{
if ($x === 0.0) {
return 0.0;
}
$tox = 2.0 / abs($x);
$bip = 0;
$ans = 0.0;
$bi = 1.0;
for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) {
$bim = $bip + $j * $tox * $bi;
$bip = $bi;
$bi = $bim;
if (abs($bi) > 1.0e+12) {
$ans *= 1.0e-12;
$bi *= 1.0e-12;
$bip *= 1.0e-12;
}
if ($j === $ord) {
$ans = $bip;
}
}
$ans *= self::besselI0($x) / $bi;
return ($x < 0.0 && (($ord % 2) === 1)) ? -$ans : $ans;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php 0000644 00000016311 15167673465 0023305 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertBinary extends ConvertBase
{
/**
* toDecimal.
*
* Return a binary value as decimal.
*
* Excel Function:
* BIN2DEC(x)
*
* @param array|bool|float|int|string $value The binary number (as a string) that you want to convert. The number
* cannot contain more than 10 characters (10 bits). The most significant
* bit of number is the sign bit. The remaining 9 bits are magnitude bits.
* Negative numbers are represented using two's-complement notation.
* If number is not a valid binary number, or if number contains more than
* 10 characters (10 bits), BIN2DEC returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toDecimal($value)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = self::validateValue($value);
$value = self::validateBinary($value);
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) == 10 && $value[0] === '1') {
// Two's Complement
$value = substr($value, -9);
return '-' . (512 - bindec($value));
}
return (string) bindec($value);
}
/**
* toHex.
*
* Return a binary value as hex.
*
* Excel Function:
* BIN2HEX(x[,places])
*
* @param array|bool|float|int|string $value The binary number (as a string) that you want to convert. The number
* cannot contain more than 10 characters (10 bits). The most significant
* bit of number is the sign bit. The remaining 9 bits are magnitude bits.
* Negative numbers are represented using two's-complement notation.
* If number is not a valid binary number, or if number contains more than
* 10 characters (10 bits), BIN2HEX returns the #NUM! error value.
* Or can be an array of values
* @param null|array|float|int|string $places The number of characters to use. If places is omitted, BIN2HEX uses the
* minimum number of characters necessary. Places is useful for padding the
* return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, BIN2HEX returns the #VALUE! error value.
* If places is negative, BIN2HEX returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toHex($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateBinary($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) == 10 && $value[0] === '1') {
$high2 = substr($value, 0, 2);
$low8 = substr($value, 2);
$xarr = ['00' => '00000000', '01' => '00000001', '10' => 'FFFFFFFE', '11' => 'FFFFFFFF'];
return $xarr[$high2] . strtoupper(substr('0' . dechex((int) bindec($low8)), -2));
}
$hexVal = (string) strtoupper(dechex((int) bindec($value)));
return self::nbrConversionFormat($hexVal, $places);
}
/**
* toOctal.
*
* Return a binary value as octal.
*
* Excel Function:
* BIN2OCT(x[,places])
*
* @param array|bool|float|int|string $value The binary number (as a string) that you want to convert. The number
* cannot contain more than 10 characters (10 bits). The most significant
* bit of number is the sign bit. The remaining 9 bits are magnitude bits.
* Negative numbers are represented using two's-complement notation.
* If number is not a valid binary number, or if number contains more than
* 10 characters (10 bits), BIN2OCT returns the #NUM! error value.
* Or can be an array of values
* @param null|array|float|int|string $places The number of characters to use. If places is omitted, BIN2OCT uses the
* minimum number of characters necessary. Places is useful for padding the
* return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, BIN2OCT returns the #VALUE! error value.
* If places is negative, BIN2OCT returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toOctal($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateBinary($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) == 10 && $value[0] === '1') { // Two's Complement
return str_repeat('7', 6) . strtoupper(decoct((int) bindec("11$value")));
}
$octVal = (string) decoct((int) bindec($value));
return self::nbrConversionFormat($octVal, $places);
}
protected static function validateBinary(string $value): string
{
if ((strlen($value) > preg_match_all('/[01]/', $value)) || (strlen($value) > 10)) {
throw new Exception(ExcelError::NAN());
}
return $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php 0000644 00000046430 15167673465 0024025 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use Complex\Complex as ComplexObject;
use Complex\Exception as ComplexException;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ComplexFunctions
{
use ArrayEnabled;
/**
* IMABS.
*
* Returns the absolute value (modulus) of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMABS(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the absolute value
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMABS(array|string $complexNumber): array|float|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return $complex->abs();
}
/**
* IMARGUMENT.
*
* Returns the argument theta of a complex number, i.e. the angle in radians from the real
* axis to the representation of the number in polar coordinates.
*
* Excel Function:
* IMARGUMENT(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the argument theta
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMARGUMENT(array|string $complexNumber): array|float|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return ExcelError::DIV0();
}
return $complex->argument();
}
/**
* IMCONJUGATE.
*
* Returns the complex conjugate of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCONJUGATE(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the conjugate
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCONJUGATE(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->conjugate();
}
/**
* IMCOS.
*
* Returns the cosine of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCOS(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the cosine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOS(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->cos();
}
/**
* IMCOSH.
*
* Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCOSH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic cosine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOSH(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->cosh();
}
/**
* IMCOT.
*
* Returns the cotangent of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCOT(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the cotangent
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOT(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->cot();
}
/**
* IMCSC.
*
* Returns the cosecant of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCSC(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the cosecant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCSC(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->csc();
}
/**
* IMCSCH.
*
* Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCSCH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic cosecant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCSCH(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->csch();
}
/**
* IMSIN.
*
* Returns the sine of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMSIN(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the sine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSIN(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->sin();
}
/**
* IMSINH.
*
* Returns the hyperbolic sine of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMSINH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic sine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSINH(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->sinh();
}
/**
* IMSEC.
*
* Returns the secant of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMSEC(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the secant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSEC(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->sec();
}
/**
* IMSECH.
*
* Returns the hyperbolic secant of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMSECH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic secant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSECH(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->sech();
}
/**
* IMTAN.
*
* Returns the tangent of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMTAN(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the tangent
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMTAN(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->tan();
}
/**
* IMSQRT.
*
* Returns the square root of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMSQRT(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the square root
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSQRT(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
$theta = self::IMARGUMENT($complexNumber);
if ($theta === ExcelError::DIV0()) {
return '0';
}
return (string) $complex->sqrt();
}
/**
* IMLN.
*
* Returns the natural logarithm of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMLN(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the natural logarithm
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLN(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return ExcelError::NAN();
}
return (string) $complex->ln();
}
/**
* IMLOG10.
*
* Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMLOG10(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the common logarithm
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLOG10(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return ExcelError::NAN();
}
return (string) $complex->log10();
}
/**
* IMLOG2.
*
* Returns the base-2 logarithm of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMLOG2(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the base-2 logarithm
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLOG2(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return ExcelError::NAN();
}
return (string) $complex->log2();
}
/**
* IMEXP.
*
* Returns the exponential of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMEXP(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the exponential
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMEXP(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->exp();
}
/**
* IMPOWER.
*
* Returns a complex number in x + yi or x + yj text format raised to a power.
*
* Excel Function:
* IMPOWER(complexNumber,realNumber)
*
* @param array|string $complexNumber the complex number you want to raise to a power
* Or can be an array of values
* @param array|float|int|string $realNumber the power to which you want to raise the complex number
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMPOWER(array|string $complexNumber, array|float|int|string $realNumber): array|string
{
if (is_array($complexNumber) || is_array($realNumber)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber, $realNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
if (!is_numeric($realNumber)) {
return ExcelError::VALUE();
}
return (string) $complex->pow((float) $realNumber);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php 0000644 00000010505 15167673465 0024172 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use Complex\Complex as ComplexObject;
use Complex\Exception as ComplexException;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ComplexOperations
{
use ArrayEnabled;
/**
* IMDIV.
*
* Returns the quotient of two complex numbers in x + yi or x + yj text format.
*
* Excel Function:
* IMDIV(complexDividend,complexDivisor)
*
* @param array|string $complexDividend the complex numerator or dividend
* Or can be an array of values
* @param array|string $complexDivisor the complex denominator or divisor
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMDIV(array|string $complexDividend, array|string $complexDivisor): array|string
{
if (is_array($complexDividend) || is_array($complexDivisor)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexDividend, $complexDivisor);
}
try {
return (string) (new ComplexObject($complexDividend))->divideby(new ComplexObject($complexDivisor));
} catch (ComplexException) {
return ExcelError::NAN();
}
}
/**
* IMSUB.
*
* Returns the difference of two complex numbers in x + yi or x + yj text format.
*
* Excel Function:
* IMSUB(complexNumber1,complexNumber2)
*
* @param array|string $complexNumber1 the complex number from which to subtract complexNumber2
* Or can be an array of values
* @param array|string $complexNumber2 the complex number to subtract from complexNumber1
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSUB(array|string $complexNumber1, array|string $complexNumber2): array|string
{
if (is_array($complexNumber1) || is_array($complexNumber2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber1, $complexNumber2);
}
try {
return (string) (new ComplexObject($complexNumber1))->subtract(new ComplexObject($complexNumber2));
} catch (ComplexException) {
return ExcelError::NAN();
}
}
/**
* IMSUM.
*
* Returns the sum of two or more complex numbers in x + yi or x + yj text format.
*
* Excel Function:
* IMSUM(complexNumber[,complexNumber[,...]])
*
* @param string ...$complexNumbers Series of complex numbers to add
*/
public static function IMSUM(...$complexNumbers): string
{
// Return value
$returnValue = new ComplexObject(0.0);
$aArgs = Functions::flattenArray($complexNumbers);
try {
// Loop through the arguments
foreach ($aArgs as $complex) {
$returnValue = $returnValue->add(new ComplexObject($complex));
}
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $returnValue;
}
/**
* IMPRODUCT.
*
* Returns the product of two or more complex numbers in x + yi or x + yj text format.
*
* Excel Function:
* IMPRODUCT(complexNumber[,complexNumber[,...]])
*
* @param string ...$complexNumbers Series of complex numbers to multiply
*/
public static function IMPRODUCT(...$complexNumbers): string
{
// Return value
$returnValue = new ComplexObject(1.0);
$aArgs = Functions::flattenArray($complexNumbers);
try {
// Loop through the arguments
foreach ($aArgs as $complex) {
$returnValue = $returnValue->multiply(new ComplexObject($complex));
}
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $returnValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php 0000644 00000017163 15167673465 0022613 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertHex extends ConvertBase
{
/**
* toBinary.
*
* Return a hex value as binary.
*
* Excel Function:
* HEX2BIN(x[,places])
*
* @param array|bool|float|string $value The hexadecimal number you want to convert.
* Number cannot contain more than 10 characters.
* The most significant bit of number is the sign bit (40th bit from the right).
* The remaining 9 bits are magnitude bits.
* Negative numbers are represented using two's-complement notation.
* If number is negative, HEX2BIN ignores places and returns a 10-character binary number.
* If number is negative, it cannot be less than FFFFFFFE00,
* and if number is positive, it cannot be greater than 1FF.
* If number is not a valid hexadecimal number, HEX2BIN returns the #NUM! error value.
* If HEX2BIN requires more than places characters, it returns the #NUM! error value.
* Or can be an array of values
* @param array|int $places The number of characters to use. If places is omitted,
* HEX2BIN uses the minimum number of characters necessary. Places
* is useful for padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, HEX2BIN returns the #VALUE! error value.
* If places is negative, HEX2BIN returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toBinary($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateHex($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$dec = self::toDecimal($value);
return ConvertDecimal::toBinary($dec, $places);
}
/**
* toDecimal.
*
* Return a hex value as decimal.
*
* Excel Function:
* HEX2DEC(x)
*
* @param array|bool|float|int|string $value The hexadecimal number you want to convert. This number cannot
* contain more than 10 characters (40 bits). The most significant
* bit of number is the sign bit. The remaining 39 bits are magnitude
* bits. Negative numbers are represented using two's-complement
* notation.
* If number is not a valid hexadecimal number, HEX2DEC returns the
* #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toDecimal($value)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = self::validateValue($value);
$value = self::validateHex($value);
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) > 10) {
return ExcelError::NAN();
}
$binX = '';
foreach (str_split($value) as $char) {
$binX .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT);
}
if (strlen($binX) == 40 && $binX[0] == '1') {
for ($i = 0; $i < 40; ++$i) {
$binX[$i] = ($binX[$i] == '1' ? '0' : '1');
}
return (string) ((bindec($binX) + 1) * -1);
}
return (string) bindec($binX);
}
/**
* toOctal.
*
* Return a hex value as octal.
*
* Excel Function:
* HEX2OCT(x[,places])
*
* @param array|bool|float|int|string $value The hexadecimal number you want to convert. Number cannot
* contain more than 10 characters. The most significant bit of
* number is the sign bit. The remaining 39 bits are magnitude
* bits. Negative numbers are represented using two's-complement
* notation.
* If number is negative, HEX2OCT ignores places and returns a
* 10-character octal number.
* If number is negative, it cannot be less than FFE0000000, and
* if number is positive, it cannot be greater than 1FFFFFFF.
* If number is not a valid hexadecimal number, HEX2OCT returns
* the #NUM! error value.
* If HEX2OCT requires more than places characters, it returns
* the #NUM! error value.
* Or can be an array of values
* @param array|int $places The number of characters to use. If places is omitted, HEX2OCT
* uses the minimum number of characters necessary. Places is
* useful for padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, HEX2OCT returns the #VALUE! error
* value.
* If places is negative, HEX2OCT returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toOctal($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateHex($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$decimal = self::toDecimal($value);
return ConvertDecimal::toOctal($decimal, $places);
}
protected static function validateHex(string $value): string
{
if (strlen($value) > preg_match_all('/[0123456789ABCDEF]/', $value)) {
throw new Exception(ExcelError::NAN());
}
return $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php 0000644 00000105516 15167673465 0022527 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertUOM
{
use ArrayEnabled;
public const CATEGORY_WEIGHT_AND_MASS = 'Weight and Mass';
public const CATEGORY_DISTANCE = 'Distance';
public const CATEGORY_TIME = 'Time';
public const CATEGORY_PRESSURE = 'Pressure';
public const CATEGORY_FORCE = 'Force';
public const CATEGORY_ENERGY = 'Energy';
public const CATEGORY_POWER = 'Power';
public const CATEGORY_MAGNETISM = 'Magnetism';
public const CATEGORY_TEMPERATURE = 'Temperature';
public const CATEGORY_VOLUME = 'Volume and Liquid Measure';
public const CATEGORY_AREA = 'Area';
public const CATEGORY_INFORMATION = 'Information';
public const CATEGORY_SPEED = 'Speed';
/**
* Details of the Units of measure that can be used in CONVERTUOM().
*
* @var array<string, array{Group: string, UnitName: string, AllowPrefix: bool}>
*/
private static array $conversionUnits = [
// Weight and Mass
'g' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Gram', 'AllowPrefix' => true],
'sg' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Slug', 'AllowPrefix' => false],
'lbm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Pound mass (avoirdupois)', 'AllowPrefix' => false],
'u' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'U (atomic mass unit)', 'AllowPrefix' => true],
'ozm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => false],
'grain' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Grain', 'AllowPrefix' => false],
'cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'U.S. (short) hundredweight', 'AllowPrefix' => false],
'shweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'U.S. (short) hundredweight', 'AllowPrefix' => false],
'uk_cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial hundredweight', 'AllowPrefix' => false],
'lcwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial hundredweight', 'AllowPrefix' => false],
'hweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial hundredweight', 'AllowPrefix' => false],
'stone' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Stone', 'AllowPrefix' => false],
'ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Ton', 'AllowPrefix' => false],
'uk_ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial ton', 'AllowPrefix' => false],
'LTON' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial ton', 'AllowPrefix' => false],
'brton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial ton', 'AllowPrefix' => false],
// Distance
'm' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Meter', 'AllowPrefix' => true],
'mi' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Statute mile', 'AllowPrefix' => false],
'Nmi' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Nautical mile', 'AllowPrefix' => false],
'in' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Inch', 'AllowPrefix' => false],
'ft' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Foot', 'AllowPrefix' => false],
'yd' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Yard', 'AllowPrefix' => false],
'ang' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Angstrom', 'AllowPrefix' => true],
'ell' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Ell', 'AllowPrefix' => false],
'ly' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Light Year', 'AllowPrefix' => false],
'parsec' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Parsec', 'AllowPrefix' => false],
'pc' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Parsec', 'AllowPrefix' => false],
'Pica' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Pica (1/72 in)', 'AllowPrefix' => false],
'Picapt' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Pica (1/72 in)', 'AllowPrefix' => false],
'pica' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Pica (1/6 in)', 'AllowPrefix' => false],
'survey_mi' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'U.S survey mile (statute mile)', 'AllowPrefix' => false],
// Time
'yr' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Year', 'AllowPrefix' => false],
'day' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Day', 'AllowPrefix' => false],
'd' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Day', 'AllowPrefix' => false],
'hr' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Hour', 'AllowPrefix' => false],
'mn' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Minute', 'AllowPrefix' => false],
'min' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Minute', 'AllowPrefix' => false],
'sec' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Second', 'AllowPrefix' => true],
's' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Second', 'AllowPrefix' => true],
// Pressure
'Pa' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'Pascal', 'AllowPrefix' => true],
'p' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'Pascal', 'AllowPrefix' => true],
'atm' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'Atmosphere', 'AllowPrefix' => true],
'at' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'Atmosphere', 'AllowPrefix' => true],
'mmHg' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'mm of Mercury', 'AllowPrefix' => true],
'psi' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'PSI', 'AllowPrefix' => true],
'Torr' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'Torr', 'AllowPrefix' => true],
// Force
'N' => ['Group' => self::CATEGORY_FORCE, 'UnitName' => 'Newton', 'AllowPrefix' => true],
'dyn' => ['Group' => self::CATEGORY_FORCE, 'UnitName' => 'Dyne', 'AllowPrefix' => true],
'dy' => ['Group' => self::CATEGORY_FORCE, 'UnitName' => 'Dyne', 'AllowPrefix' => true],
'lbf' => ['Group' => self::CATEGORY_FORCE, 'UnitName' => 'Pound force', 'AllowPrefix' => false],
'pond' => ['Group' => self::CATEGORY_FORCE, 'UnitName' => 'Pond', 'AllowPrefix' => true],
// Energy
'J' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Joule', 'AllowPrefix' => true],
'e' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Erg', 'AllowPrefix' => true],
'c' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Thermodynamic calorie', 'AllowPrefix' => true],
'cal' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'IT calorie', 'AllowPrefix' => true],
'eV' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Electron volt', 'AllowPrefix' => true],
'ev' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Electron volt', 'AllowPrefix' => true],
'HPh' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Horsepower-hour', 'AllowPrefix' => false],
'hh' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Horsepower-hour', 'AllowPrefix' => false],
'Wh' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Watt-hour', 'AllowPrefix' => true],
'wh' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Watt-hour', 'AllowPrefix' => true],
'flb' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Foot-pound', 'AllowPrefix' => false],
'BTU' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'BTU', 'AllowPrefix' => false],
'btu' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'BTU', 'AllowPrefix' => false],
// Power
'HP' => ['Group' => self::CATEGORY_POWER, 'UnitName' => 'Horsepower', 'AllowPrefix' => false],
'h' => ['Group' => self::CATEGORY_POWER, 'UnitName' => 'Horsepower', 'AllowPrefix' => false],
'W' => ['Group' => self::CATEGORY_POWER, 'UnitName' => 'Watt', 'AllowPrefix' => true],
'w' => ['Group' => self::CATEGORY_POWER, 'UnitName' => 'Watt', 'AllowPrefix' => true],
'PS' => ['Group' => self::CATEGORY_POWER, 'UnitName' => 'Pferdestärke', 'AllowPrefix' => false],
// Magnetism
'T' => ['Group' => self::CATEGORY_MAGNETISM, 'UnitName' => 'Tesla', 'AllowPrefix' => true],
'ga' => ['Group' => self::CATEGORY_MAGNETISM, 'UnitName' => 'Gauss', 'AllowPrefix' => true],
// Temperature
'C' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Celsius', 'AllowPrefix' => false],
'cel' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Celsius', 'AllowPrefix' => false],
'F' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Fahrenheit', 'AllowPrefix' => false],
'fah' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Fahrenheit', 'AllowPrefix' => false],
'K' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Kelvin', 'AllowPrefix' => false],
'kel' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Kelvin', 'AllowPrefix' => false],
'Rank' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Rankine', 'AllowPrefix' => false],
'Reau' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Réaumur', 'AllowPrefix' => false],
// Volume
'l' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Litre', 'AllowPrefix' => true],
'L' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Litre', 'AllowPrefix' => true],
'lt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Litre', 'AllowPrefix' => true],
'tsp' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Teaspoon', 'AllowPrefix' => false],
'tspm' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Modern Teaspoon', 'AllowPrefix' => false],
'tbs' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Tablespoon', 'AllowPrefix' => false],
'oz' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Fluid Ounce', 'AllowPrefix' => false],
'cup' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cup', 'AllowPrefix' => false],
'pt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'U.S. Pint', 'AllowPrefix' => false],
'us_pt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'U.S. Pint', 'AllowPrefix' => false],
'uk_pt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'U.K. Pint', 'AllowPrefix' => false],
'qt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Quart', 'AllowPrefix' => false],
'uk_qt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Imperial Quart (UK)', 'AllowPrefix' => false],
'gal' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Gallon', 'AllowPrefix' => false],
'uk_gal' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Imperial Gallon (UK)', 'AllowPrefix' => false],
'ang3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Angstrom', 'AllowPrefix' => true],
'ang^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Angstrom', 'AllowPrefix' => true],
'barrel' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'US Oil Barrel', 'AllowPrefix' => false],
'bushel' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'US Bushel', 'AllowPrefix' => false],
'in3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Inch', 'AllowPrefix' => false],
'in^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Inch', 'AllowPrefix' => false],
'ft3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Foot', 'AllowPrefix' => false],
'ft^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Foot', 'AllowPrefix' => false],
'ly3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Light Year', 'AllowPrefix' => false],
'ly^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Light Year', 'AllowPrefix' => false],
'm3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Meter', 'AllowPrefix' => true],
'm^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Meter', 'AllowPrefix' => true],
'mi3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Mile', 'AllowPrefix' => false],
'mi^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Mile', 'AllowPrefix' => false],
'yd3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Yard', 'AllowPrefix' => false],
'yd^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Yard', 'AllowPrefix' => false],
'Nmi3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Nautical Mile', 'AllowPrefix' => false],
'Nmi^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Nautical Mile', 'AllowPrefix' => false],
'Pica3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Pica', 'AllowPrefix' => false],
'Pica^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Pica', 'AllowPrefix' => false],
'Picapt3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Pica', 'AllowPrefix' => false],
'Picapt^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Pica', 'AllowPrefix' => false],
'GRT' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Gross Registered Ton', 'AllowPrefix' => false],
'regton' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Gross Registered Ton', 'AllowPrefix' => false],
'MTON' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Measurement Ton (Freight Ton)', 'AllowPrefix' => false],
// Area
'ha' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Hectare', 'AllowPrefix' => true],
'uk_acre' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'International Acre', 'AllowPrefix' => false],
'us_acre' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'US Survey/Statute Acre', 'AllowPrefix' => false],
'ang2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Angstrom', 'AllowPrefix' => true],
'ang^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Angstrom', 'AllowPrefix' => true],
'ar' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Are', 'AllowPrefix' => true],
'ft2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Feet', 'AllowPrefix' => false],
'ft^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Feet', 'AllowPrefix' => false],
'in2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Inches', 'AllowPrefix' => false],
'in^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Inches', 'AllowPrefix' => false],
'ly2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Light Years', 'AllowPrefix' => false],
'ly^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Light Years', 'AllowPrefix' => false],
'm2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Meters', 'AllowPrefix' => true],
'm^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Meters', 'AllowPrefix' => true],
'Morgen' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Morgen', 'AllowPrefix' => false],
'mi2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Miles', 'AllowPrefix' => false],
'mi^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Miles', 'AllowPrefix' => false],
'Nmi2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Nautical Miles', 'AllowPrefix' => false],
'Nmi^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Nautical Miles', 'AllowPrefix' => false],
'Pica2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Pica', 'AllowPrefix' => false],
'Pica^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Pica', 'AllowPrefix' => false],
'Picapt2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Pica', 'AllowPrefix' => false],
'Picapt^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Pica', 'AllowPrefix' => false],
'yd2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Yards', 'AllowPrefix' => false],
'yd^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Yards', 'AllowPrefix' => false],
// Information
'byte' => ['Group' => self::CATEGORY_INFORMATION, 'UnitName' => 'Byte', 'AllowPrefix' => true],
'bit' => ['Group' => self::CATEGORY_INFORMATION, 'UnitName' => 'Bit', 'AllowPrefix' => true],
// Speed
'm/s' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Meters per second', 'AllowPrefix' => true],
'm/sec' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Meters per second', 'AllowPrefix' => true],
'm/h' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Meters per hour', 'AllowPrefix' => true],
'm/hr' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Meters per hour', 'AllowPrefix' => true],
'mph' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Miles per hour', 'AllowPrefix' => false],
'admkn' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Admiralty Knot', 'AllowPrefix' => false],
'kn' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Knot', 'AllowPrefix' => false],
];
/**
* Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
*
* @var array<string, array{multiplier: float, name: string}>
*/
private static array $conversionMultipliers = [
'Y' => ['multiplier' => 1E24, 'name' => 'yotta'],
'Z' => ['multiplier' => 1E21, 'name' => 'zetta'],
'E' => ['multiplier' => 1E18, 'name' => 'exa'],
'P' => ['multiplier' => 1E15, 'name' => 'peta'],
'T' => ['multiplier' => 1E12, 'name' => 'tera'],
'G' => ['multiplier' => 1E9, 'name' => 'giga'],
'M' => ['multiplier' => 1E6, 'name' => 'mega'],
'k' => ['multiplier' => 1E3, 'name' => 'kilo'],
'h' => ['multiplier' => 1E2, 'name' => 'hecto'],
'e' => ['multiplier' => 1E1, 'name' => 'dekao'],
'da' => ['multiplier' => 1E1, 'name' => 'dekao'],
'd' => ['multiplier' => 1E-1, 'name' => 'deci'],
'c' => ['multiplier' => 1E-2, 'name' => 'centi'],
'm' => ['multiplier' => 1E-3, 'name' => 'milli'],
'u' => ['multiplier' => 1E-6, 'name' => 'micro'],
'n' => ['multiplier' => 1E-9, 'name' => 'nano'],
'p' => ['multiplier' => 1E-12, 'name' => 'pico'],
'f' => ['multiplier' => 1E-15, 'name' => 'femto'],
'a' => ['multiplier' => 1E-18, 'name' => 'atto'],
'z' => ['multiplier' => 1E-21, 'name' => 'zepto'],
'y' => ['multiplier' => 1E-24, 'name' => 'yocto'],
];
/**
* Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
*
** @var array<string, array{multiplier: float|int, name: string}>
*/
private static array $binaryConversionMultipliers = [
'Yi' => ['multiplier' => 2 ** 80, 'name' => 'yobi'],
'Zi' => ['multiplier' => 2 ** 70, 'name' => 'zebi'],
'Ei' => ['multiplier' => 2 ** 60, 'name' => 'exbi'],
'Pi' => ['multiplier' => 2 ** 50, 'name' => 'pebi'],
'Ti' => ['multiplier' => 2 ** 40, 'name' => 'tebi'],
'Gi' => ['multiplier' => 2 ** 30, 'name' => 'gibi'],
'Mi' => ['multiplier' => 2 ** 20, 'name' => 'mebi'],
'ki' => ['multiplier' => 2 ** 10, 'name' => 'kibi'],
];
/**
* Details of the Units of measure conversion factors, organised by group.
*
* @var array<string, array<string, float>>
*/
private static array $unitConversions = [
// Conversion uses gram (g) as an intermediate unit
self::CATEGORY_WEIGHT_AND_MASS => [
'g' => 1.0,
'sg' => 6.85217658567918E-05,
'lbm' => 2.20462262184878E-03,
'u' => 6.02214179421676E+23,
'ozm' => 3.52739619495804E-02,
'grain' => 1.54323583529414E+01,
'cwt' => 2.20462262184878E-05,
'shweight' => 2.20462262184878E-05,
'uk_cwt' => 1.96841305522212E-05,
'lcwt' => 1.96841305522212E-05,
'hweight' => 1.96841305522212E-05,
'stone' => 1.57473044417770E-04,
'ton' => 1.10231131092439E-06,
'uk_ton' => 9.84206527611061E-07,
'LTON' => 9.84206527611061E-07,
'brton' => 9.84206527611061E-07,
],
// Conversion uses meter (m) as an intermediate unit
self::CATEGORY_DISTANCE => [
'm' => 1.0,
'mi' => 6.21371192237334E-04,
'Nmi' => 5.39956803455724E-04,
'in' => 3.93700787401575E+01,
'ft' => 3.28083989501312E+00,
'yd' => 1.09361329833771E+00,
'ang' => 1.0E+10,
'ell' => 8.74890638670166E-01,
'ly' => 1.05700083402462E-16,
'parsec' => 3.24077928966473E-17,
'pc' => 3.24077928966473E-17,
'Pica' => 2.83464566929134E+03,
'Picapt' => 2.83464566929134E+03,
'pica' => 2.36220472440945E+02,
'survey_mi' => 6.21369949494950E-04,
],
// Conversion uses second (s) as an intermediate unit
self::CATEGORY_TIME => [
'yr' => 3.16880878140289E-08,
'day' => 1.15740740740741E-05,
'd' => 1.15740740740741E-05,
'hr' => 2.77777777777778E-04,
'mn' => 1.66666666666667E-02,
'min' => 1.66666666666667E-02,
'sec' => 1.0,
's' => 1.0,
],
// Conversion uses Pascal (Pa) as an intermediate unit
self::CATEGORY_PRESSURE => [
'Pa' => 1.0,
'p' => 1.0,
'atm' => 9.86923266716013E-06,
'at' => 9.86923266716013E-06,
'mmHg' => 7.50063755419211E-03,
'psi' => 1.45037737730209E-04,
'Torr' => 7.50061682704170E-03,
],
// Conversion uses Newton (N) as an intermediate unit
self::CATEGORY_FORCE => [
'N' => 1.0,
'dyn' => 1.0E+5,
'dy' => 1.0E+5,
'lbf' => 2.24808923655339E-01,
'pond' => 1.01971621297793E+02,
],
// Conversion uses Joule (J) as an intermediate unit
self::CATEGORY_ENERGY => [
'J' => 1.0,
'e' => 9.99999519343231E+06,
'c' => 2.39006249473467E-01,
'cal' => 2.38846190642017E-01,
'eV' => 6.24145700000000E+18,
'ev' => 6.24145700000000E+18,
'HPh' => 3.72506430801000E-07,
'hh' => 3.72506430801000E-07,
'Wh' => 2.77777916238711E-04,
'wh' => 2.77777916238711E-04,
'flb' => 2.37304222192651E+01,
'BTU' => 9.47815067349015E-04,
'btu' => 9.47815067349015E-04,
],
// Conversion uses Horsepower (HP) as an intermediate unit
self::CATEGORY_POWER => [
'HP' => 1.0,
'h' => 1.0,
'W' => 7.45699871582270E+02,
'w' => 7.45699871582270E+02,
'PS' => 1.01386966542400E+00,
],
// Conversion uses Tesla (T) as an intermediate unit
self::CATEGORY_MAGNETISM => [
'T' => 1.0,
'ga' => 10000.0,
],
// Conversion uses litre (l) as an intermediate unit
self::CATEGORY_VOLUME => [
'l' => 1.0,
'L' => 1.0,
'lt' => 1.0,
'tsp' => 2.02884136211058E+02,
'tspm' => 2.0E+02,
'tbs' => 6.76280454036860E+01,
'oz' => 3.38140227018430E+01,
'cup' => 4.22675283773038E+00,
'pt' => 2.11337641886519E+00,
'us_pt' => 2.11337641886519E+00,
'uk_pt' => 1.75975398639270E+00,
'qt' => 1.05668820943259E+00,
'uk_qt' => 8.79876993196351E-01,
'gal' => 2.64172052358148E-01,
'uk_gal' => 2.19969248299088E-01,
'ang3' => 1.0E+27,
'ang^3' => 1.0E+27,
'barrel' => 6.28981077043211E-03,
'bushel' => 2.83775932584017E-02,
'in3' => 6.10237440947323E+01,
'in^3' => 6.10237440947323E+01,
'ft3' => 3.53146667214886E-02,
'ft^3' => 3.53146667214886E-02,
'ly3' => 1.18093498844171E-51,
'ly^3' => 1.18093498844171E-51,
'm3' => 1.0E-03,
'm^3' => 1.0E-03,
'mi3' => 2.39912758578928E-13,
'mi^3' => 2.39912758578928E-13,
'yd3' => 1.30795061931439E-03,
'yd^3' => 1.30795061931439E-03,
'Nmi3' => 1.57426214685811E-13,
'Nmi^3' => 1.57426214685811E-13,
'Pica3' => 2.27769904358706E+07,
'Pica^3' => 2.27769904358706E+07,
'Picapt3' => 2.27769904358706E+07,
'Picapt^3' => 2.27769904358706E+07,
'GRT' => 3.53146667214886E-04,
'regton' => 3.53146667214886E-04,
'MTON' => 8.82866668037215E-04,
],
// Conversion uses hectare (ha) as an intermediate unit
self::CATEGORY_AREA => [
'ha' => 1.0,
'uk_acre' => 2.47105381467165E+00,
'us_acre' => 2.47104393046628E+00,
'ang2' => 1.0E+24,
'ang^2' => 1.0E+24,
'ar' => 1.0E+02,
'ft2' => 1.07639104167097E+05,
'ft^2' => 1.07639104167097E+05,
'in2' => 1.55000310000620E+07,
'in^2' => 1.55000310000620E+07,
'ly2' => 1.11725076312873E-28,
'ly^2' => 1.11725076312873E-28,
'm2' => 1.0E+04,
'm^2' => 1.0E+04,
'Morgen' => 4.0E+00,
'mi2' => 3.86102158542446E-03,
'mi^2' => 3.86102158542446E-03,
'Nmi2' => 2.91553349598123E-03,
'Nmi^2' => 2.91553349598123E-03,
'Pica2' => 8.03521607043214E+10,
'Pica^2' => 8.03521607043214E+10,
'Picapt2' => 8.03521607043214E+10,
'Picapt^2' => 8.03521607043214E+10,
'yd2' => 1.19599004630108E+04,
'yd^2' => 1.19599004630108E+04,
],
// Conversion uses bit (bit) as an intermediate unit
self::CATEGORY_INFORMATION => [
'bit' => 1.0,
'byte' => 0.125,
],
// Conversion uses Meters per Second (m/s) as an intermediate unit
self::CATEGORY_SPEED => [
'm/s' => 1.0,
'm/sec' => 1.0,
'm/h' => 3.60E+03,
'm/hr' => 3.60E+03,
'mph' => 2.23693629205440E+00,
'admkn' => 1.94260256941567E+00,
'kn' => 1.94384449244060E+00,
],
];
/**
* getConversionGroups
* Returns a list of the different conversion groups for UOM conversions.
*/
public static function getConversionCategories(): array
{
$conversionGroups = [];
foreach (self::$conversionUnits as $conversionUnit) {
$conversionGroups[] = $conversionUnit['Group'];
}
return array_merge(array_unique($conversionGroups));
}
/**
* getConversionGroupUnits
* Returns an array of units of measure, for a specified conversion group, or for all groups.
*
* @param ?string $category The group whose units of measure you want to retrieve
*/
public static function getConversionCategoryUnits(?string $category = null): array
{
$conversionGroups = [];
foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) {
if (($category === null) || ($conversionGroup['Group'] == $category)) {
$conversionGroups[$conversionGroup['Group']][] = $conversionUnit;
}
}
return $conversionGroups;
}
/**
* getConversionGroupUnitDetails.
*
* @param ?string $category The group whose units of measure you want to retrieve
*/
public static function getConversionCategoryUnitDetails(?string $category = null): array
{
$conversionGroups = [];
foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) {
if (($category === null) || ($conversionGroup['Group'] == $category)) {
$conversionGroups[$conversionGroup['Group']][] = [
'unit' => $conversionUnit,
'description' => $conversionGroup['UnitName'],
];
}
}
return $conversionGroups;
}
/**
* getConversionMultipliers
* Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
*
* @return mixed[]
*/
public static function getConversionMultipliers(): array
{
return self::$conversionMultipliers;
}
/**
* getBinaryConversionMultipliers
* Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure in CONVERTUOM().
*
* @return mixed[]
*/
public static function getBinaryConversionMultipliers(): array
{
return self::$binaryConversionMultipliers;
}
/**
* CONVERT.
*
* Converts a number from one measurement system to another.
* For example, CONVERT can translate a table of distances in miles to a table of distances
* in kilometers.
*
* Excel Function:
* CONVERT(value,fromUOM,toUOM)
*
* @param array|float|int|string $value the value in fromUOM to convert
* Or can be an array of values
* @param array|string $fromUOM the units for value
* Or can be an array of values
* @param array|string $toUOM the units for the result
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function CONVERT($value, $fromUOM, $toUOM)
{
if (is_array($value) || is_array($fromUOM) || is_array($toUOM)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $fromUOM, $toUOM);
}
if (!is_numeric($value)) {
return ExcelError::VALUE();
}
try {
[$fromUOM, $fromCategory, $fromMultiplier] = self::getUOMDetails($fromUOM);
[$toUOM, $toCategory, $toMultiplier] = self::getUOMDetails($toUOM);
} catch (Exception) {
return ExcelError::NA();
}
if ($fromCategory !== $toCategory) {
return ExcelError::NA();
}
// @var float $value
$value *= $fromMultiplier;
if (($fromUOM === $toUOM) && ($fromMultiplier === $toMultiplier)) {
// We've already factored $fromMultiplier into the value, so we need
// to reverse it again
return $value / $fromMultiplier;
} elseif ($fromUOM === $toUOM) {
return $value / $toMultiplier;
} elseif ($fromCategory === self::CATEGORY_TEMPERATURE) {
return self::convertTemperature($fromUOM, $toUOM, $value);
}
$baseValue = $value * (1.0 / self::$unitConversions[$fromCategory][$fromUOM]);
return ($baseValue * self::$unitConversions[$fromCategory][$toUOM]) / $toMultiplier;
}
private static function getUOMDetails(string $uom): array
{
if (isset(self::$conversionUnits[$uom])) {
$unitCategory = self::$conversionUnits[$uom]['Group'];
return [$uom, $unitCategory, 1.0];
}
// Check 1-character standard metric multiplier prefixes
$multiplierType = substr($uom, 0, 1);
$uom = substr($uom, 1);
if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) {
if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
throw new Exception('Prefix not allowed for UoM');
}
$unitCategory = self::$conversionUnits[$uom]['Group'];
return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']];
}
$multiplierType .= substr($uom, 0, 1);
$uom = substr($uom, 1);
// Check 2-character standard metric multiplier prefixes
if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) {
if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
throw new Exception('Prefix not allowed for UoM');
}
$unitCategory = self::$conversionUnits[$uom]['Group'];
return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']];
}
// Check 2-character binary multiplier prefixes
if (isset(self::$conversionUnits[$uom], self::$binaryConversionMultipliers[$multiplierType])) {
if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
throw new Exception('Prefix not allowed for UoM');
}
$unitCategory = self::$conversionUnits[$uom]['Group'];
if ($unitCategory !== 'Information') {
throw new Exception('Binary Prefix is only allowed for Information UoM');
}
return [$uom, $unitCategory, self::$binaryConversionMultipliers[$multiplierType]['multiplier']];
}
throw new Exception('UoM Not Found');
}
protected static function convertTemperature(string $fromUOM, string $toUOM, float|int $value): float|int
{
$fromUOM = self::resolveTemperatureSynonyms($fromUOM);
$toUOM = self::resolveTemperatureSynonyms($toUOM);
if ($fromUOM === $toUOM) {
return $value;
}
// Convert to Kelvin
switch ($fromUOM) {
case 'F':
$value = ($value - 32) / 1.8 + 273.15;
break;
case 'C':
$value += 273.15;
break;
case 'Rank':
$value /= 1.8;
break;
case 'Reau':
$value = $value * 1.25 + 273.15;
break;
}
// Convert from Kelvin
switch ($toUOM) {
case 'F':
$value = ($value - 273.15) * 1.8 + 32.00;
break;
case 'C':
$value -= 273.15;
break;
case 'Rank':
$value *= 1.8;
break;
case 'Reau':
$value = ($value - 273.15) * 0.80000;
break;
}
return $value;
}
private static function resolveTemperatureSynonyms(string $uom): string
{
return match ($uom) {
'fah' => 'F',
'cel' => 'C',
'kel' => 'K',
default => $uom,
};
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php 0000644 00000020251 15167673465 0022064 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BitWise
{
use ArrayEnabled;
const SPLIT_DIVISOR = 2 ** 24;
/**
* Split a number into upper and lower portions for full 32-bit support.
*
* @return int[]
*/
private static function splitNumber(float|int $number): array
{
return [(int) floor($number / self::SPLIT_DIVISOR), (int) fmod($number, self::SPLIT_DIVISOR)];
}
/**
* BITAND.
*
* Returns the bitwise AND of two integer values.
*
* Excel Function:
* BITAND(number1, number2)
*
* @param null|array|bool|float|int|string $number1 Or can be an array of values
* @param null|array|bool|float|int|string $number2 Or can be an array of values
*
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITAND(null|array|bool|float|int|string $number1, null|array|bool|float|int|string $number2): array|string|int|float
{
if (is_array($number1) || is_array($number2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
}
try {
$number1 = self::validateBitwiseArgument($number1);
$number2 = self::validateBitwiseArgument($number2);
} catch (Exception $e) {
return $e->getMessage();
}
$split1 = self::splitNumber($number1);
$split2 = self::splitNumber($number2);
return self::SPLIT_DIVISOR * ($split1[0] & $split2[0]) + ($split1[1] & $split2[1]);
}
/**
* BITOR.
*
* Returns the bitwise OR of two integer values.
*
* Excel Function:
* BITOR(number1, number2)
*
* @param null|array|bool|float|int|string $number1 Or can be an array of values
* @param null|array|bool|float|int|string $number2 Or can be an array of values
*
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITOR(null|array|bool|float|int|string $number1, null|array|bool|float|int|string $number2): array|string|int|float
{
if (is_array($number1) || is_array($number2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
}
try {
$number1 = self::validateBitwiseArgument($number1);
$number2 = self::validateBitwiseArgument($number2);
} catch (Exception $e) {
return $e->getMessage();
}
$split1 = self::splitNumber($number1);
$split2 = self::splitNumber($number2);
return self::SPLIT_DIVISOR * ($split1[0] | $split2[0]) + ($split1[1] | $split2[1]);
}
/**
* BITXOR.
*
* Returns the bitwise XOR of two integer values.
*
* Excel Function:
* BITXOR(number1, number2)
*
* @param null|array|bool|float|int|string $number1 Or can be an array of values
* @param null|array|bool|float|int|string $number2 Or can be an array of values
*
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITXOR(null|array|bool|float|int|string $number1, null|array|bool|float|int|string $number2): array|string|int|float
{
if (is_array($number1) || is_array($number2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
}
try {
$number1 = self::validateBitwiseArgument($number1);
$number2 = self::validateBitwiseArgument($number2);
} catch (Exception $e) {
return $e->getMessage();
}
$split1 = self::splitNumber($number1);
$split2 = self::splitNumber($number2);
return self::SPLIT_DIVISOR * ($split1[0] ^ $split2[0]) + ($split1[1] ^ $split2[1]);
}
/**
* BITLSHIFT.
*
* Returns the number value shifted left by shift_amount bits.
*
* Excel Function:
* BITLSHIFT(number, shift_amount)
*
* @param null|array|bool|float|int|string $number Or can be an array of values
* @param null|array|bool|float|int|string $shiftAmount Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITLSHIFT(null|array|bool|float|int|string $number, null|array|bool|float|int|string $shiftAmount): array|string|float
{
if (is_array($number) || is_array($shiftAmount)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount);
}
try {
$number = self::validateBitwiseArgument($number);
$shiftAmount = self::validateShiftAmount($shiftAmount);
} catch (Exception $e) {
return $e->getMessage();
}
$result = floor($number * (2 ** $shiftAmount));
if ($result > 2 ** 48 - 1) {
return ExcelError::NAN();
}
return $result;
}
/**
* BITRSHIFT.
*
* Returns the number value shifted right by shift_amount bits.
*
* Excel Function:
* BITRSHIFT(number, shift_amount)
*
* @param null|array|bool|float|int|string $number Or can be an array of values
* @param null|array|bool|float|int|string $shiftAmount Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITRSHIFT(null|array|bool|float|int|string $number, null|array|bool|float|int|string $shiftAmount): array|string|float
{
if (is_array($number) || is_array($shiftAmount)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount);
}
try {
$number = self::validateBitwiseArgument($number);
$shiftAmount = self::validateShiftAmount($shiftAmount);
} catch (Exception $e) {
return $e->getMessage();
}
$result = floor($number / (2 ** $shiftAmount));
if ($result > 2 ** 48 - 1) { // possible because shiftAmount can be negative
return ExcelError::NAN();
}
return $result;
}
/**
* Validate arguments passed to the bitwise functions.
*/
private static function validateBitwiseArgument(mixed $value): float
{
$value = self::nullFalseTrueToNumber($value);
if (is_numeric($value)) {
$value = (float) $value;
if ($value == floor($value)) {
if (($value > 2 ** 48 - 1) || ($value < 0)) {
throw new Exception(ExcelError::NAN());
}
return floor($value);
}
throw new Exception(ExcelError::NAN());
}
throw new Exception(ExcelError::VALUE());
}
/**
* Validate arguments passed to the bitwise functions.
*/
private static function validateShiftAmount(mixed $value): int
{
$value = self::nullFalseTrueToNumber($value);
if (is_numeric($value)) {
if (abs($value) > 53) {
throw new Exception(ExcelError::NAN());
}
return (int) $value;
}
throw new Exception(ExcelError::VALUE());
}
/**
* Many functions accept null/false/true argument treated as 0/0/1.
*/
private static function nullFalseTrueToNumber(mixed &$number): mixed
{
if ($number === null) {
$number = 0;
} elseif (is_bool($number)) {
$number = (int) $number;
}
return $number;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php 0000644 00000010245 15167673465 0022050 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselK
{
use ArrayEnabled;
/**
* BESSELK.
*
* Returns the modified Bessel function Kn(x), which is equivalent to the Bessel functions evaluated
* for purely imaginary arguments.
*
* Excel Function:
* BESSELK(x,ord)
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELK returns the #VALUE! error value.
* Or can be an array of values
* @param mixed $ord The integer order of the Bessel function.
* If ord is not an integer, it is truncated.
* If $ord is nonnumeric, BESSELK returns the #VALUE! error value.
* If $ord < 0, BESSELKI returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELK(mixed $x, mixed $ord): array|string|float
{
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
$ord = EngineeringValidations::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if (($ord < 0) || ($x <= 0.0)) {
return ExcelError::NAN();
}
$fBk = self::calculate($x, $ord);
return (is_nan($fBk)) ? ExcelError::NAN() : $fBk;
}
private static function calculate(float $x, int $ord): float
{
return match ($ord) {
0 => self::besselK0($x),
1 => self::besselK1($x),
default => self::besselK2($x, $ord),
};
}
/**
* Mollify Phpstan.
*
* @codeCoverageIgnore
*/
private static function callBesselI(float $x, int $ord): float
{
$rslt = BesselI::BESSELI($x, $ord);
if (!is_float($rslt)) {
throw new Exception('Unexpected array or string');
}
return $rslt;
}
private static function besselK0(float $x): float
{
if ($x <= 2) {
$fNum2 = $x * 0.5;
$y = ($fNum2 * $fNum2);
return -log($fNum2) * self::callBesselI($x, 0)
+ (-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y
* (0.10750e-3 + $y * 0.74e-5))))));
}
$y = 2 / $x;
return exp(-$x) / sqrt($x)
* (1.25331414 + $y * (-0.7832358e-1 + $y * (0.2189568e-1 + $y * (-0.1062446e-1 + $y
* (0.587872e-2 + $y * (-0.251540e-2 + $y * 0.53208e-3))))));
}
private static function besselK1(float $x): float
{
if ($x <= 2) {
$fNum2 = $x * 0.5;
$y = ($fNum2 * $fNum2);
return log($fNum2) * self::callBesselI($x, 1)
+ (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y
* (-0.110404e-2 + $y * (-0.4686e-4))))))) / $x;
}
$y = 2 / $x;
return exp(-$x) / sqrt($x)
* (1.25331414 + $y * (0.23498619 + $y * (-0.3655620e-1 + $y * (0.1504268e-1 + $y * (-0.780353e-2 + $y
* (0.325614e-2 + $y * (-0.68245e-3)))))));
}
private static function besselK2(float $x, int $ord): float
{
$fTox = 2 / $x;
$fBkm = self::besselK0($x);
$fBk = self::besselK1($x);
for ($n = 1; $n < $ord; ++$n) {
$fBkp = $fBkm + $n * $fTox * $fBk;
$fBkm = $fBk;
$fBk = $fBkp;
}
return $fBk;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php 0000644 00000011425 15167673465 0022067 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselY
{
use ArrayEnabled;
/**
* BESSELY.
*
* Returns the Bessel function, which is also called the Weber function or the Neumann function.
*
* Excel Function:
* BESSELY(x,ord)
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELY returns the #VALUE! error value.
* Or can be an array of values
* @param mixed $ord The integer order of the Bessel function.
* If ord is not an integer, it is truncated.
* If $ord is nonnumeric, BESSELY returns the #VALUE! error value.
* If $ord < 0, BESSELY returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELY(mixed $x, mixed $ord): array|string|float
{
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
$ord = EngineeringValidations::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if (($ord < 0) || ($x <= 0.0)) {
return ExcelError::NAN();
}
$fBy = self::calculate($x, $ord);
return (is_nan($fBy)) ? ExcelError::NAN() : $fBy;
}
private static function calculate(float $x, int $ord): float
{
return match ($ord) {
0 => self::besselY0($x),
1 => self::besselY1($x),
default => self::besselY2($x, $ord),
};
}
/**
* Mollify Phpstan.
*
* @codeCoverageIgnore
*/
private static function callBesselJ(float $x, int $ord): float
{
$rslt = BesselJ::BESSELJ($x, $ord);
if (!is_float($rslt)) {
throw new Exception('Unexpected array or string');
}
return $rslt;
}
private static function besselY0(float $x): float
{
if ($x < 8.0) {
$y = ($x * $x);
$ans1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y
* (-86327.92757 + $y * 228.4622733))));
$ans2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y
* (47447.26470 + $y * (226.1030244 + $y))));
return $ans1 / $ans2 + 0.636619772 * self::callBesselJ($x, 0) * log($x);
}
$z = 8.0 / $x;
$y = ($z * $z);
$xx = $x - 0.785398164;
$ans1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
$ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y
* (-0.934945152e-7))));
return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2);
}
private static function besselY1(float $x): float
{
if ($x < 8.0) {
$y = ($x * $x);
$ans1 = $x * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y
* (0.7349264551e9 + $y * (-0.4237922726e7 + $y * 0.8511937935e4)))));
$ans2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y
* (0.1020426050e6 + $y * (0.3549632885e3 + $y)))));
return ($ans1 / $ans2) + 0.636619772 * (self::callBesselJ($x, 1) * log($x) - 1 / $x);
}
$z = 8.0 / $x;
$y = $z * $z;
$xx = $x - 2.356194491;
$ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6))));
$ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y
* (-0.88228987e-6 + $y * 0.105787412e-6)));
return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2);
}
private static function besselY2(float $x, int $ord): float
{
$fTox = 2.0 / $x;
$fBym = self::besselY0($x);
$fBy = self::besselY1($x);
for ($n = 1; $n < $ord; ++$n) {
$fByp = $n * $fTox * $fBy - $fBym;
$fBym = $fBy;
$fBy = $fByp;
}
return $fBy;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php 0000644 00000001177 15167673465 0025154 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class EngineeringValidations
{
public static function validateFloat(mixed $value): float
{
if (!is_numeric($value)) {
throw new Exception(ExcelError::VALUE());
}
return (float) $value;
}
public static function validateInt(mixed $value): int
{
if (!is_numeric($value)) {
throw new Exception(ExcelError::VALUE());
}
return (int) floor((float) $value);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php 0000644 00000010307 15167673465 0022126 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use Complex\Complex as ComplexObject;
use Complex\Exception as ComplexException;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Complex
{
use ArrayEnabled;
/**
* COMPLEX.
*
* Converts real and imaginary coefficients into a complex number of the form x +/- yi or x +/- yj.
*
* Excel Function:
* COMPLEX(realNumber,imaginary[,suffix])
*
* @param mixed $realNumber the real float coefficient of the complex number
* Or can be an array of values
* @param mixed $imaginary the imaginary float coefficient of the complex number
* Or can be an array of values
* @param mixed $suffix The character suffix for the imaginary component of the complex number.
* If omitted, the suffix is assumed to be "i".
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function COMPLEX(mixed $realNumber = 0.0, mixed $imaginary = 0.0, mixed $suffix = 'i'): array|string
{
if (is_array($realNumber) || is_array($imaginary) || is_array($suffix)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $realNumber, $imaginary, $suffix);
}
$realNumber = $realNumber ?? 0.0;
$imaginary = $imaginary ?? 0.0;
$suffix = $suffix ?? 'i';
try {
$realNumber = EngineeringValidations::validateFloat($realNumber);
$imaginary = EngineeringValidations::validateFloat($imaginary);
} catch (Exception $e) {
return $e->getMessage();
}
if (($suffix === 'i') || ($suffix === 'j') || ($suffix === '')) {
$complex = new ComplexObject($realNumber, $imaginary, $suffix);
return (string) $complex;
}
return ExcelError::VALUE();
}
/**
* IMAGINARY.
*
* Returns the imaginary coefficient of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMAGINARY(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the imaginary
* coefficient
* Or can be an array of values
*
* @return array|float|string (string if an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMAGINARY($complexNumber): array|string|float
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return $complex->getImaginary();
}
/**
* IMREAL.
*
* Returns the real coefficient of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMREAL(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the real coefficient
* Or can be an array of values
*
* @return array|float|string (string if an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMREAL($complexNumber): array|string|float
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return $complex->getReal();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php 0000644 00000016726 15167673465 0023135 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertOctal extends ConvertBase
{
/**
* toBinary.
*
* Return an octal value as binary.
*
* Excel Function:
* OCT2BIN(x[,places])
*
* @param array|bool|float|int|string $value The octal number you want to convert. Number may not
* contain more than 10 characters. The most significant
* bit of number is the sign bit. The remaining 29 bits
* are magnitude bits. Negative numbers are represented
* using two's-complement notation.
* If number is negative, OCT2BIN ignores places and returns
* a 10-character binary number.
* If number is negative, it cannot be less than 7777777000,
* and if number is positive, it cannot be greater than 777.
* If number is not a valid octal number, OCT2BIN returns
* the #NUM! error value.
* If OCT2BIN requires more than places characters, it
* returns the #NUM! error value.
* Or can be an array of values
* @param array|int $places The number of characters to use. If places is omitted,
* OCT2BIN uses the minimum number of characters necessary.
* Places is useful for padding the return value with
* leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, OCT2BIN returns the #VALUE!
* error value.
* If places is negative, OCT2BIN returns the #NUM! error
* value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toBinary($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateOctal($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
return ConvertDecimal::toBinary(self::toDecimal($value), $places);
}
/**
* toDecimal.
*
* Return an octal value as decimal.
*
* Excel Function:
* OCT2DEC(x)
*
* @param array|bool|float|int|string $value The octal number you want to convert. Number may not contain
* more than 10 octal characters (30 bits). The most significant
* bit of number is the sign bit. The remaining 29 bits are
* magnitude bits. Negative numbers are represented using
* two's-complement notation.
* If number is not a valid octal number, OCT2DEC returns the
* #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toDecimal($value)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = self::validateValue($value);
$value = self::validateOctal($value);
} catch (Exception $e) {
return $e->getMessage();
}
$binX = '';
foreach (str_split($value) as $char) {
$binX .= str_pad(decbin((int) $char), 3, '0', STR_PAD_LEFT);
}
if (strlen($binX) == 30 && $binX[0] == '1') {
for ($i = 0; $i < 30; ++$i) {
$binX[$i] = ($binX[$i] == '1' ? '0' : '1');
}
return (string) ((bindec($binX) + 1) * -1);
}
return (string) bindec($binX);
}
/**
* toHex.
*
* Return an octal value as hex.
*
* Excel Function:
* OCT2HEX(x[,places])
*
* @param array|bool|float|int|string $value The octal number you want to convert. Number may not contain
* more than 10 octal characters (30 bits). The most significant
* bit of number is the sign bit. The remaining 29 bits are
* magnitude bits. Negative numbers are represented using
* two's-complement notation.
* If number is negative, OCT2HEX ignores places and returns a
* 10-character hexadecimal number.
* If number is not a valid octal number, OCT2HEX returns the
* #NUM! error value.
* If OCT2HEX requires more than places characters, it returns
* the #NUM! error value.
* Or can be an array of values
* @param array|int $places The number of characters to use. If places is omitted, OCT2HEX
* uses the minimum number of characters necessary. Places is useful
* for padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, OCT2HEX returns the #VALUE! error value.
* If places is negative, OCT2HEX returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toHex($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateOctal($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$hexVal = strtoupper(dechex((int) self::toDecimal($value)));
$hexVal = (PHP_INT_SIZE === 4 && strlen($value) === 10 && $value[0] >= '4') ? "FF{$hexVal}" : $hexVal;
return self::nbrConversionFormat($hexVal, $places);
}
protected static function validateOctal(string $value): string
{
$numDigits = (int) preg_match_all('/[01234567]/', $value);
if (strlen($value) > $numDigits || $numDigits > 10) {
throw new Exception(ExcelError::NAN());
}
return $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php 0000644 00000003603 15167673465 0022733 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
abstract class ConvertBase
{
use ArrayEnabled;
protected static function validateValue(mixed $value): string
{
if (is_bool($value)) {
if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
throw new Exception(ExcelError::VALUE());
}
$value = (int) $value;
}
if (is_numeric($value)) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
$value = floor((float) $value);
}
}
return strtoupper((string) $value);
}
protected static function validatePlaces(mixed $places = null): ?int
{
if ($places === null) {
return $places;
}
if (is_numeric($places)) {
if ($places < 0 || $places > 10) {
throw new Exception(ExcelError::NAN());
}
return (int) $places;
}
throw new Exception(ExcelError::VALUE());
}
/**
* Formats a number base string value with leading zeroes.
*
* @param string $value The "number" to pad
* @param ?int $places The length that we want to pad this value
*
* @return string The padded "number"
*/
protected static function nbrConversionFormat(string $value, ?int $places): string
{
if ($places !== null) {
if (strlen($value) <= $places) {
return substr(str_pad($value, $places, '0', STR_PAD_LEFT), -10);
}
return ExcelError::NAN();
}
return substr($value, -10);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Erf.php 0000644 00000007077 15167673465 0021245 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Erf
{
use ArrayEnabled;
private const TWO_SQRT_PI = 1.128379167095512574;
/**
* ERF.
*
* Returns the error function integrated between the lower and upper bound arguments.
*
* Note: In Excel 2007 or earlier, if you input a negative value for the upper or lower bound arguments,
* the function would return a #NUM! error. However, in Excel 2010, the function algorithm was
* improved, so that it can now calculate the function for both positive and negative ranges.
* PhpSpreadsheet follows Excel 2010 behaviour, and accepts negative arguments.
*
* Excel Function:
* ERF(lower[,upper])
*
* @param mixed $lower Lower bound float for integrating ERF
* Or can be an array of values
* @param mixed $upper Upper bound float for integrating ERF.
* If omitted, ERF integrates between zero and lower_limit
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERF(mixed $lower, mixed $upper = null): array|float|string
{
if (is_array($lower) || is_array($upper)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $lower, $upper);
}
if (is_numeric($lower)) {
if ($upper === null) {
return self::erfValue($lower);
}
if (is_numeric($upper)) {
return self::erfValue($upper) - self::erfValue($lower);
}
}
return ExcelError::VALUE();
}
/**
* ERFPRECISE.
*
* Returns the error function integrated between the lower and upper bound arguments.
*
* Excel Function:
* ERF.PRECISE(limit)
*
* @param mixed $limit Float bound for integrating ERF, other bound is zero
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERFPRECISE(mixed $limit)
{
if (is_array($limit)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $limit);
}
return self::ERF($limit);
}
private static function makeFloat(mixed $value): float
{
return is_numeric($value) ? ((float) $value) : 0.0;
}
/**
* Method to calculate the erf value.
*/
public static function erfValue(float|int|string $value): float
{
$value = (float) $value;
if (abs($value) > 2.2) {
return 1 - self::makeFloat(ErfC::ERFC($value));
}
$sum = $term = $value;
$xsqr = ($value * $value);
$j = 1;
do {
$term *= $xsqr / $j;
$sum -= $term / (2 * $j + 1);
++$j;
$term *= $xsqr / $j;
$sum += $term / (2 * $j + 1);
++$j;
if ($sum == 0.0) {
break;
}
} while (abs($term / $sum) > Functions::PRECISION);
return self::TWO_SQRT_PI * $sum;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php 0000644 00000004535 15167673465 0021344 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ErfC
{
use ArrayEnabled;
/**
* ERFC.
*
* Returns the complementary ERF function integrated between x and infinity
*
* Note: In Excel 2007 or earlier, if you input a negative value for the lower bound argument,
* the function would return a #NUM! error. However, in Excel 2010, the function algorithm was
* improved, so that it can now calculate the function for both positive and negative x values.
* PhpSpreadsheet follows Excel 2010 behaviour, and accepts nagative arguments.
*
* Excel Function:
* ERFC(x)
*
* @param mixed $value The float lower bound for integrating ERFC
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERFC(mixed $value)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if (is_numeric($value)) {
return self::erfcValue($value);
}
return ExcelError::VALUE();
}
private const ONE_SQRT_PI = 0.564189583547756287;
/**
* Method to calculate the erfc value.
*/
private static function erfcValue(float|int|string $value): float|int
{
$value = (float) $value;
if (abs($value) < 2.2) {
return 1 - Erf::erfValue($value);
}
if ($value < 0) {
return 2 - self::erfcValue(-$value);
}
$a = $n = 1;
$b = $c = $value;
$d = ($value * $value) + 0.5;
$q2 = $b / $d;
do {
$t = $a * $n + $b * $value;
$a = $b;
$b = $t;
$t = $c * $n + $d * $value;
$c = $d;
$d = $t;
$n += 0.5;
$q1 = $q2;
$q2 = $b / $d;
} while ((abs($q1 - $q2) / $q2) > Functions::PRECISION);
return self::ONE_SQRT_PI * exp(-$value * $value) * $q2;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php 0000644 00000014227 15167673465 0024160 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class ArrayArgumentProcessor
{
private static ArrayArgumentHelper $arrayArgumentHelper;
public static function processArguments(
ArrayArgumentHelper $arrayArgumentHelper,
callable $method,
mixed ...$arguments
): array {
self::$arrayArgumentHelper = $arrayArgumentHelper;
if (self::$arrayArgumentHelper->hasArrayArgument() === false) {
return [$method(...$arguments)];
}
if (self::$arrayArgumentHelper->arrayArguments() === 1) {
$nthArgument = self::$arrayArgumentHelper->getFirstArrayArgumentNumber();
return self::evaluateNthArgumentAsArray($method, $nthArgument, ...$arguments);
}
$singleRowVectorIndex = self::$arrayArgumentHelper->getSingleRowVector();
$singleColumnVectorIndex = self::$arrayArgumentHelper->getSingleColumnVector();
if ($singleRowVectorIndex !== null && $singleColumnVectorIndex !== null) {
// Basic logic for a single row vector and a single column vector
return self::evaluateVectorPair($method, $singleRowVectorIndex, $singleColumnVectorIndex, ...$arguments);
}
$matrixPair = self::$arrayArgumentHelper->getMatrixPair();
if ($matrixPair !== []) {
if (
(self::$arrayArgumentHelper->isVector($matrixPair[0]) === true
&& self::$arrayArgumentHelper->isVector($matrixPair[1]) === false)
|| (self::$arrayArgumentHelper->isVector($matrixPair[0]) === false
&& self::$arrayArgumentHelper->isVector($matrixPair[1]) === true)
) {
// Logic for a matrix and a vector (row or column)
return self::evaluateVectorMatrixPair($method, $matrixPair, ...$arguments);
}
// Logic for matrix/matrix, column vector/column vector or row vector/row vector
return self::evaluateMatrixPair($method, $matrixPair, ...$arguments);
}
// Still need to work out the logic for more than two array arguments,
// For the moment, we're throwing an Exception when we initialise the ArrayArgumentHelper
return ['#VALUE!'];
}
private static function evaluateVectorMatrixPair(callable $method, array $matrixIndexes, mixed ...$arguments): array
{
$matrix2 = array_pop($matrixIndexes);
/** @var array $matrixValues2 */
$matrixValues2 = $arguments[$matrix2];
$matrix1 = array_pop($matrixIndexes);
/** @var array $matrixValues1 */
$matrixValues1 = $arguments[$matrix1];
$rows = min(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
$columns = min(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
if ($rows === 1) {
$rows = max(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
}
if ($columns === 1) {
$columns = max(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
}
$result = [];
for ($rowIndex = 0; $rowIndex < $rows; ++$rowIndex) {
for ($columnIndex = 0; $columnIndex < $columns; ++$columnIndex) {
$rowIndex1 = self::$arrayArgumentHelper->isRowVector($matrix1) ? 0 : $rowIndex;
$columnIndex1 = self::$arrayArgumentHelper->isColumnVector($matrix1) ? 0 : $columnIndex;
$value1 = $matrixValues1[$rowIndex1][$columnIndex1];
$rowIndex2 = self::$arrayArgumentHelper->isRowVector($matrix2) ? 0 : $rowIndex;
$columnIndex2 = self::$arrayArgumentHelper->isColumnVector($matrix2) ? 0 : $columnIndex;
$value2 = $matrixValues2[$rowIndex2][$columnIndex2];
$arguments[$matrix1] = $value1;
$arguments[$matrix2] = $value2;
$result[$rowIndex][$columnIndex] = $method(...$arguments);
}
}
return $result;
}
private static function evaluateMatrixPair(callable $method, array $matrixIndexes, mixed ...$arguments): array
{
$matrix2 = array_pop($matrixIndexes);
/** @var array $matrixValues2 */
$matrixValues2 = $arguments[$matrix2];
$matrix1 = array_pop($matrixIndexes);
/** @var array $matrixValues1 */
$matrixValues1 = $arguments[$matrix1];
$result = [];
foreach ($matrixValues1 as $rowIndex => $row) {
foreach ($row as $columnIndex => $value1) {
if (isset($matrixValues2[$rowIndex][$columnIndex]) === false) {
continue;
}
$value2 = $matrixValues2[$rowIndex][$columnIndex];
$arguments[$matrix1] = $value1;
$arguments[$matrix2] = $value2;
$result[$rowIndex][$columnIndex] = $method(...$arguments);
}
}
return $result;
}
private static function evaluateVectorPair(callable $method, int $rowIndex, int $columnIndex, mixed ...$arguments): array
{
$rowVector = Functions::flattenArray($arguments[$rowIndex]);
$columnVector = Functions::flattenArray($arguments[$columnIndex]);
$result = [];
foreach ($columnVector as $column) {
$rowResults = [];
foreach ($rowVector as $row) {
$arguments[$rowIndex] = $row;
$arguments[$columnIndex] = $column;
$rowResults[] = $method(...$arguments);
}
$result[] = $rowResults;
}
return $result;
}
/**
* Note, offset is from 1 (for the first argument) rather than from 0.
*/
private static function evaluateNthArgumentAsArray(callable $method, int $nthArgument, mixed ...$arguments): array
{
$values = array_slice($arguments, $nthArgument - 1, 1);
/** @var array $values */
$values = array_pop($values);
$result = [];
foreach ($values as $value) {
$arguments[$nthArgument - 1] = $value;
$result[] = $method(...$arguments);
}
return $result;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php 0000644 00000030661 15167673465 0025235 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine\Operands;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use Stringable;
final class StructuredReference implements Operand, Stringable
{
public const NAME = 'Structured Reference';
private const OPEN_BRACE = '[';
private const CLOSE_BRACE = ']';
private const ITEM_SPECIFIER_ALL = '#All';
private const ITEM_SPECIFIER_HEADERS = '#Headers';
private const ITEM_SPECIFIER_DATA = '#Data';
private const ITEM_SPECIFIER_TOTALS = '#Totals';
private const ITEM_SPECIFIER_THIS_ROW = '#This Row';
private const ITEM_SPECIFIER_ROWS_SET = [
self::ITEM_SPECIFIER_ALL,
self::ITEM_SPECIFIER_HEADERS,
self::ITEM_SPECIFIER_DATA,
self::ITEM_SPECIFIER_TOTALS,
];
private const TABLE_REFERENCE = '/([\p{L}_\\\\][\p{L}\p{N}\._]+)?(\[(?:[^\]\[]+|(?R))*+\])/miu';
private string $value;
private string $tableName;
private Table $table;
private string $reference;
private ?int $headersRow;
private int $firstDataRow;
private int $lastDataRow;
private ?int $totalsRow;
private array $columns;
public function __construct(string $structuredReference)
{
$this->value = $structuredReference;
}
public static function fromParser(string $formula, int $index, array $matches): self
{
$val = $matches[0];
$srCount = substr_count($val, self::OPEN_BRACE)
- substr_count($val, self::CLOSE_BRACE);
while ($srCount > 0) {
$srIndex = strlen($val);
$srStringRemainder = substr($formula, $index + $srIndex);
$closingPos = strpos($srStringRemainder, self::CLOSE_BRACE);
if ($closingPos === false) {
throw new Exception("Formula Error: No closing ']' to match opening '['");
}
$srStringRemainder = substr($srStringRemainder, 0, $closingPos + 1);
--$srCount;
if (str_contains($srStringRemainder, self::OPEN_BRACE)) {
++$srCount;
}
$val .= $srStringRemainder;
}
return new self($val);
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
public function parse(Cell $cell): string
{
$this->getTableStructure($cell);
$cellRange = ($this->isRowReference()) ? $this->getRowReference($cell) : $this->getColumnReference();
$sheetName = '';
$worksheet = $this->table->getWorksheet();
if ($worksheet !== null && $worksheet !== $cell->getWorksheet()) {
$sheetName = "'" . $worksheet->getTitle() . "'!";
}
return $sheetName . $cellRange;
}
private function isRowReference(): bool
{
return str_contains($this->value, '[@')
|| str_contains($this->value, '[' . self::ITEM_SPECIFIER_THIS_ROW . ']');
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
private function getTableStructure(Cell $cell): void
{
preg_match(self::TABLE_REFERENCE, $this->value, $matches);
$this->tableName = $matches[1];
$this->table = ($this->tableName === '')
? $this->getTableForCell($cell)
: $this->getTableByName($cell);
$this->reference = $matches[2];
$tableRange = Coordinate::getRangeBoundaries($this->table->getRange());
$this->headersRow = ($this->table->getShowHeaderRow()) ? (int) $tableRange[0][1] : null;
$this->firstDataRow = ($this->table->getShowHeaderRow()) ? (int) $tableRange[0][1] + 1 : $tableRange[0][1];
$this->totalsRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] : null;
$this->lastDataRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] - 1 : $tableRange[1][1];
$cellParam = $cell;
$worksheet = $this->table->getWorksheet();
if ($worksheet !== null && $worksheet !== $cell->getWorksheet()) {
$cellParam = $worksheet->getCell('A1');
}
$this->columns = $this->getColumns($cellParam, $tableRange);
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
private function getTableForCell(Cell $cell): Table
{
$tables = $cell->getWorksheet()->getTableCollection();
foreach ($tables as $table) {
/** @var Table $table */
$range = $table->getRange();
if ($cell->isInRange($range) === true) {
$this->tableName = $table->getName();
return $table;
}
}
throw new Exception('Table for Structured Reference cannot be identified');
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
private function getTableByName(Cell $cell): Table
{
$table = $cell->getWorksheet()->getTableByName($this->tableName);
if ($table === null) {
$spreadsheet = $cell->getWorksheet()->getParent();
if ($spreadsheet !== null) {
$table = $spreadsheet->getTableByName($this->tableName);
}
}
if ($table === null) {
throw new Exception("Table {$this->tableName} for Structured Reference cannot be located");
}
return $table;
}
private function getColumns(Cell $cell, array $tableRange): array
{
$worksheet = $cell->getWorksheet();
$cellReference = $cell->getCoordinate();
$columns = [];
$lastColumn = ++$tableRange[1][0];
for ($column = $tableRange[0][0]; $column !== $lastColumn; ++$column) {
$columns[$column] = $worksheet
->getCell($column . ($this->headersRow ?? ($this->firstDataRow - 1)))
->getCalculatedValue();
}
$worksheet->getCell($cellReference);
return $columns;
}
private function getRowReference(Cell $cell): string
{
$reference = str_replace("\u{a0}", ' ', $this->reference);
/** @var string $reference */
$reference = str_replace('[' . self::ITEM_SPECIFIER_THIS_ROW . '],', '', $reference);
foreach ($this->columns as $columnId => $columnName) {
$columnName = str_replace("\u{a0}", ' ', $columnName);
$reference = $this->adjustRowReference($columnName, $reference, $cell, $columnId);
}
return $this->validateParsedReference(trim($reference, '[]@, '));
}
private function adjustRowReference(string $columnName, string $reference, Cell $cell, string $columnId): string
{
if ($columnName !== '') {
$cellReference = $columnId . $cell->getRow();
$pattern1 = '/\[' . preg_quote($columnName, '/') . '\]/miu';
$pattern2 = '/@' . preg_quote($columnName, '/') . '/miu';
if (preg_match($pattern1, $reference) === 1) {
$reference = preg_replace($pattern1, $cellReference, $reference);
} elseif (preg_match($pattern2, $reference) === 1) {
$reference = preg_replace($pattern2, $cellReference, $reference);
}
/** @var string $reference */
}
return $reference;
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
private function getColumnReference(): string
{
$reference = str_replace("\u{a0}", ' ', $this->reference);
$startRow = ($this->totalsRow === null) ? $this->lastDataRow : $this->totalsRow;
$endRow = ($this->headersRow === null) ? $this->firstDataRow : $this->headersRow;
[$startRow, $endRow] = $this->getRowsForColumnReference($reference, $startRow, $endRow);
$reference = $this->getColumnsForColumnReference($reference, $startRow, $endRow);
$reference = trim($reference, '[]@, ');
if (substr_count($reference, ':') > 1) {
$cells = explode(':', $reference);
$firstCell = array_shift($cells);
$lastCell = array_pop($cells);
$reference = "{$firstCell}:{$lastCell}";
}
return $this->validateParsedReference($reference);
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
private function validateParsedReference(string $reference): string
{
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . ':' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $reference) !== 1) {
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $reference) !== 1) {
throw new Exception(
"Invalid Structured Reference {$this->reference} {$reference}",
Exception::CALCULATION_ENGINE_PUSH_TO_STACK
);
}
}
return $reference;
}
private function fullData(int $startRow, int $endRow): string
{
$columns = array_keys($this->columns);
$firstColumn = array_shift($columns);
$lastColumn = (empty($columns)) ? $firstColumn : array_pop($columns);
return "{$firstColumn}{$startRow}:{$lastColumn}{$endRow}";
}
private function getMinimumRow(string $reference): int
{
return match ($reference) {
self::ITEM_SPECIFIER_ALL, self::ITEM_SPECIFIER_HEADERS => $this->headersRow ?? $this->firstDataRow,
self::ITEM_SPECIFIER_DATA => $this->firstDataRow,
self::ITEM_SPECIFIER_TOTALS => $this->totalsRow ?? $this->lastDataRow,
default => $this->headersRow ?? $this->firstDataRow,
};
}
private function getMaximumRow(string $reference): int
{
return match ($reference) {
self::ITEM_SPECIFIER_HEADERS => $this->headersRow ?? $this->firstDataRow,
self::ITEM_SPECIFIER_DATA => $this->lastDataRow,
self::ITEM_SPECIFIER_ALL, self::ITEM_SPECIFIER_TOTALS => $this->totalsRow ?? $this->lastDataRow,
default => $this->totalsRow ?? $this->lastDataRow,
};
}
public function value(): string
{
return $this->value;
}
/**
* @return array<int, int>
*/
private function getRowsForColumnReference(string &$reference, int $startRow, int $endRow): array
{
$rowsSelected = false;
foreach (self::ITEM_SPECIFIER_ROWS_SET as $rowReference) {
$pattern = '/\[' . $rowReference . '\]/mui';
if (preg_match($pattern, $reference) === 1) {
if (($rowReference === self::ITEM_SPECIFIER_HEADERS) && ($this->table->getShowHeaderRow() === false)) {
throw new Exception(
'Table Headers are Hidden, and should not be Referenced',
Exception::CALCULATION_ENGINE_PUSH_TO_STACK
);
}
$rowsSelected = true;
$startRow = min($startRow, $this->getMinimumRow($rowReference));
$endRow = max($endRow, $this->getMaximumRow($rowReference));
$reference = preg_replace($pattern, '', $reference) ?? '';
}
}
if ($rowsSelected === false) {
// If there isn't any Special Item Identifier specified, then the selection defaults to data rows only.
$startRow = $this->firstDataRow;
$endRow = $this->lastDataRow;
}
return [$startRow, $endRow];
}
private function getColumnsForColumnReference(string $reference, int $startRow, int $endRow): string
{
$columnsSelected = false;
foreach ($this->columns as $columnId => $columnName) {
$columnName = str_replace("\u{a0}", ' ', $columnName ?? '');
$cellFrom = "{$columnId}{$startRow}";
$cellTo = "{$columnId}{$endRow}";
$cellReference = ($cellFrom === $cellTo) ? $cellFrom : "{$cellFrom}:{$cellTo}";
$pattern = '/\[' . preg_quote($columnName, '/') . '\]/mui';
if (preg_match($pattern, $reference) === 1) {
$columnsSelected = true;
$reference = preg_replace($pattern, $cellReference, $reference);
}
/** @var string $reference */
}
if ($columnsSelected === false) {
return $this->fullData($startRow, $endRow);
}
return $reference;
}
public function __toString(): string
{
return $this->value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php 0000644 00000000336 15167673465 0022636 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine\Operands;
interface Operand
{
public static function fromParser(string $formula, int $index, array $matches): self;
public function value(): string;
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php 0000644 00000002240 15167673465 0023502 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
class CyclicReferenceStack
{
/**
* The call stack for calculated cells.
*
* @var mixed[]
*/
private array $stack = [];
/**
* Return the number of entries on the stack.
*/
public function count(): int
{
return count($this->stack);
}
/**
* Push a new entry onto the stack.
*/
public function push(mixed $value): void
{
$this->stack[$value] = $value;
}
/**
* Pop the last entry from the stack.
*/
public function pop(): mixed
{
return array_pop($this->stack);
}
/**
* Test to see if a specified entry exists on the stack.
*
* @param mixed $value The value to test
*/
public function onStack(mixed $value): bool
{
return isset($this->stack[$value]);
}
/**
* Clear the stack.
*/
public function clear(): void
{
$this->stack = [];
}
/**
* Return an array of all entries on the stack.
*
* @return mixed[]
*/
public function showStack(): array
{
return $this->stack;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php 0000644 00000013565 15167673465 0022601 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class FormattedNumber
{
/** Constants */
/** Regular Expressions */
private const STRING_REGEXP_FRACTION = '~^\s*(-?)((\d*)\s+)?(\d+\/\d+)\s*$~';
private const STRING_REGEXP_PERCENT = '~^(?:(?: *(?<PrefixedSign>[-+])? *\% *(?<PrefixedSign2>[-+])? *(?<PrefixedValue>[0-9]+\.?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?<PostfixedSign>[-+])? *(?<PostfixedValue>[0-9]+\.?[0-9]*(?:E[-+]?[0-9]*)?) *\% *))$~i';
// preg_quoted string for major currency symbols, with a %s for locale currency
private const CURRENCY_CONVERSION_LIST = '\$€£¥%s';
private const STRING_CONVERSION_LIST = [
[self::class, 'convertToNumberIfNumeric'],
[self::class, 'convertToNumberIfFraction'],
[self::class, 'convertToNumberIfPercent'],
[self::class, 'convertToNumberIfCurrency'],
];
/**
* Identify whether a string contains a formatted numeric value,
* and convert it to a numeric if it is.
*
* @param string $operand string value to test
*/
public static function convertToNumberIfFormatted(string &$operand): bool
{
foreach (self::STRING_CONVERSION_LIST as $conversionMethod) {
if ($conversionMethod($operand) === true) {
return true;
}
}
return false;
}
/**
* Identify whether a string contains a numeric value,
* and convert it to a numeric if it is.
*
* @param string $operand string value to test
*/
public static function convertToNumberIfNumeric(string &$operand): bool
{
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
$value = preg_replace(['/(\d)' . $thousandsSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1$2', '$1$2'], trim($operand));
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
if (is_numeric($value)) {
$operand = (float) $value;
return true;
}
return false;
}
/**
* Identify whether a string contains a fractional numeric value,
* and convert it to a numeric if it is.
*
* @param string $operand string value to test
*/
public static function convertToNumberIfFraction(string &$operand): bool
{
if (preg_match(self::STRING_REGEXP_FRACTION, $operand, $match)) {
$sign = ($match[1] === '-') ? '-' : '+';
$wholePart = ($match[3] === '') ? '' : ($sign . $match[3]);
$fractionFormula = '=' . $wholePart . $sign . $match[4];
$operand = Calculation::getInstance()->_calculateFormulaValue($fractionFormula);
return true;
}
return false;
}
/**
* Identify whether a string contains a percentage, and if so,
* convert it to a numeric.
*
* @param string $operand string value to test
*/
public static function convertToNumberIfPercent(string &$operand): bool
{
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', trim($operand));
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
$match = [];
if ($value !== null && preg_match(self::STRING_REGEXP_PERCENT, $value, $match, PREG_UNMATCHED_AS_NULL)) {
//Calculate the percentage
$sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? '';
$operand = (float) ($sign . ($match['PostfixedValue'] ?? $match['PrefixedValue'])) / 100;
return true;
}
return false;
}
/**
* Identify whether a string contains a currency value, and if so,
* convert it to a numeric.
*
* @param string $operand string value to test
*/
public static function convertToNumberIfCurrency(string &$operand): bool
{
$currencyRegexp = self::currencyMatcherRegexp();
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $operand);
$match = [];
if ($value !== null && preg_match($currencyRegexp, $value, $match, PREG_UNMATCHED_AS_NULL)) {
//Determine the sign
$sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? '';
$decimalSeparator = StringHelper::getDecimalSeparator();
//Cast to a float
$intermediate = (string) ($match['PostfixedValue'] ?? $match['PrefixedValue']);
$intermediate = str_replace($decimalSeparator, '.', $intermediate);
if (is_numeric($intermediate)) {
$operand = (float) ($sign . str_replace($decimalSeparator, '.', $intermediate));
return true;
}
}
return false;
}
public static function currencyMatcherRegexp(): string
{
$currencyCodes = sprintf(self::CURRENCY_CONVERSION_LIST, preg_quote(StringHelper::getCurrencyCode(), '/'));
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
return '~^(?:(?: *(?<PrefixedSign>[-+])? *(?<PrefixedCurrency>[' . $currencyCodes . ']) *(?<PrefixedSign2>[-+])? *(?<PrefixedValue>[0-9]+[' . $decimalSeparator . ']?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?<PostfixedSign>[-+])? *(?<PostfixedValue>[0-9]+' . $decimalSeparator . '?[0-9]*(?:E[-+]?[0-9]*)?) *(?<PostfixedCurrency>[' . $currencyCodes . ']) *))$~ui';
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/BranchPruner.php 0000644 00000013775 15167673465 0022077 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class BranchPruner
{
protected bool $branchPruningEnabled;
/**
* Used to generate unique store keys.
*/
private int $branchStoreKeyCounter = 0;
/**
* currently pending storeKey (last item of the storeKeysStack.
*/
protected ?string $pendingStoreKey = null;
/**
* @var string[]
*/
protected array $storeKeysStack = [];
/**
* @var bool[]
*/
protected array $conditionMap = [];
/**
* @var bool[]
*/
protected array $thenMap = [];
/**
* @var bool[]
*/
protected array $elseMap = [];
/**
* @var int[]
*/
protected array $braceDepthMap = [];
protected ?string $currentCondition = null;
protected ?string $currentOnlyIf = null;
protected ?string $currentOnlyIfNot = null;
protected ?string $previousStoreKey = null;
public function __construct(bool $branchPruningEnabled)
{
$this->branchPruningEnabled = $branchPruningEnabled;
}
public function clearBranchStore(): void
{
$this->branchStoreKeyCounter = 0;
}
public function initialiseForLoop(): void
{
$this->currentCondition = null;
$this->currentOnlyIf = null;
$this->currentOnlyIfNot = null;
$this->previousStoreKey = null;
$this->pendingStoreKey = empty($this->storeKeysStack) ? null : end($this->storeKeysStack);
if ($this->branchPruningEnabled) {
$this->initialiseCondition();
$this->initialiseThen();
$this->initialiseElse();
}
}
private function initialiseCondition(): void
{
if (isset($this->conditionMap[$this->pendingStoreKey]) && $this->conditionMap[$this->pendingStoreKey]) {
$this->currentCondition = $this->pendingStoreKey;
$stackDepth = count($this->storeKeysStack);
if ($stackDepth > 1) {
// nested if
$this->previousStoreKey = $this->storeKeysStack[$stackDepth - 2];
}
}
}
private function initialiseThen(): void
{
if (isset($this->thenMap[$this->pendingStoreKey]) && $this->thenMap[$this->pendingStoreKey]) {
$this->currentOnlyIf = $this->pendingStoreKey;
} elseif (
isset($this->previousStoreKey, $this->thenMap[$this->previousStoreKey])
&& $this->thenMap[$this->previousStoreKey]
) {
$this->currentOnlyIf = $this->previousStoreKey;
}
}
private function initialiseElse(): void
{
if (isset($this->elseMap[$this->pendingStoreKey]) && $this->elseMap[$this->pendingStoreKey]) {
$this->currentOnlyIfNot = $this->pendingStoreKey;
} elseif (
isset($this->previousStoreKey, $this->elseMap[$this->previousStoreKey])
&& $this->elseMap[$this->previousStoreKey]
) {
$this->currentOnlyIfNot = $this->previousStoreKey;
}
}
public function decrementDepth(): void
{
if (!empty($this->pendingStoreKey)) {
--$this->braceDepthMap[$this->pendingStoreKey];
}
}
public function incrementDepth(): void
{
if (!empty($this->pendingStoreKey)) {
++$this->braceDepthMap[$this->pendingStoreKey];
}
}
public function functionCall(string $functionName): void
{
if ($this->branchPruningEnabled && ($functionName === 'IF(')) {
// we handle a new if
$this->pendingStoreKey = $this->getUnusedBranchStoreKey();
$this->storeKeysStack[] = $this->pendingStoreKey;
$this->conditionMap[$this->pendingStoreKey] = true;
$this->braceDepthMap[$this->pendingStoreKey] = 0;
} elseif (!empty($this->pendingStoreKey) && array_key_exists($this->pendingStoreKey, $this->braceDepthMap)) {
// this is not an if but we go deeper
++$this->braceDepthMap[$this->pendingStoreKey];
}
}
public function argumentSeparator(): void
{
if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === 0) {
// We must go to the IF next argument
if ($this->conditionMap[$this->pendingStoreKey]) {
$this->conditionMap[$this->pendingStoreKey] = false;
$this->thenMap[$this->pendingStoreKey] = true;
} elseif ($this->thenMap[$this->pendingStoreKey]) {
$this->thenMap[$this->pendingStoreKey] = false;
$this->elseMap[$this->pendingStoreKey] = true;
} elseif ($this->elseMap[$this->pendingStoreKey]) {
throw new Exception('Reaching fourth argument of an IF');
}
}
}
public function closingBrace(mixed $value): void
{
if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === -1) {
// we are closing an IF(
if ($value !== 'IF(') {
throw new Exception('Parser bug we should be in an "IF("');
}
if ($this->conditionMap[$this->pendingStoreKey]) {
throw new Exception('We should not be expecting a condition');
}
$this->thenMap[$this->pendingStoreKey] = false;
$this->elseMap[$this->pendingStoreKey] = false;
--$this->braceDepthMap[$this->pendingStoreKey];
array_pop($this->storeKeysStack);
$this->pendingStoreKey = null;
}
}
public function currentCondition(): ?string
{
return $this->currentCondition;
}
public function currentOnlyIf(): ?string
{
return $this->currentOnlyIf;
}
public function currentOnlyIfNot(): ?string
{
return $this->currentOnlyIfNot;
}
private function getUnusedBranchStoreKey(): string
{
$storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter;
++$this->branchStoreKeyCounter;
return $storeKeyValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php 0000644 00000006247 15167673465 0020721 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
class Logger
{
/**
* Flag to determine whether a debug log should be generated by the calculation engine
* If true, then a debug log will be generated
* If false, then a debug log will not be generated.
*/
private bool $writeDebugLog = false;
/**
* Flag to determine whether a debug log should be echoed by the calculation engine
* If true, then a debug log will be echoed
* If false, then a debug log will not be echoed
* A debug log can only be echoed if it is generated.
*/
private bool $echoDebugLog = false;
/**
* The debug log generated by the calculation engine.
*
* @var string[]
*/
private array $debugLog = [];
/**
* The calculation engine cell reference stack.
*/
private CyclicReferenceStack $cellStack;
/**
* Instantiate a Calculation engine logger.
*/
public function __construct(CyclicReferenceStack $stack)
{
$this->cellStack = $stack;
}
/**
* Enable/Disable Calculation engine logging.
*/
public function setWriteDebugLog(bool $writeDebugLog): void
{
$this->writeDebugLog = $writeDebugLog;
}
/**
* Return whether calculation engine logging is enabled or disabled.
*/
public function getWriteDebugLog(): bool
{
return $this->writeDebugLog;
}
/**
* Enable/Disable echoing of debug log information.
*/
public function setEchoDebugLog(bool $echoDebugLog): void
{
$this->echoDebugLog = $echoDebugLog;
}
/**
* Return whether echoing of debug log information is enabled or disabled.
*/
public function getEchoDebugLog(): bool
{
return $this->echoDebugLog;
}
/**
* Write an entry to the calculation engine debug log.
*/
public function writeDebugLog(string $message, mixed ...$args): void
{
// Only write the debug log if logging is enabled
if ($this->writeDebugLog) {
$message = sprintf($message, ...$args);
$cellReference = implode(' -> ', $this->cellStack->showStack());
if ($this->echoDebugLog) {
echo $cellReference,
($this->cellStack->count() > 0 ? ' => ' : ''),
$message,
PHP_EOL;
}
$this->debugLog[] = $cellReference
. ($this->cellStack->count() > 0 ? ' => ' : '')
. $message;
}
}
/**
* Write a series of entries to the calculation engine debug log.
*
* @param string[] $args
*/
public function mergeDebugLog(array $args): void
{
if ($this->writeDebugLog) {
foreach ($args as $entry) {
$this->writeDebugLog($entry);
}
}
}
/**
* Clear the calculation engine debug log.
*/
public function clearLog(): void
{
$this->debugLog = [];
}
/**
* Return the calculation engine debug log.
*
* @return string[]
*/
public function getLog(): array
{
return $this->debugLog;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php 0000644 00000012011 15167673465 0023405 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class ArrayArgumentHelper
{
protected int $indexStart = 0;
protected array $arguments;
protected int $argumentCount;
protected array $rows;
protected array $columns;
public function initialise(array $arguments): void
{
$keys = array_keys($arguments);
$this->indexStart = (int) array_shift($keys);
$this->rows = $this->rows($arguments);
$this->columns = $this->columns($arguments);
$this->argumentCount = count($arguments);
$this->arguments = $this->flattenSingleCellArrays($arguments, $this->rows, $this->columns);
$this->rows = $this->rows($arguments);
$this->columns = $this->columns($arguments);
if ($this->arrayArguments() > 2) {
throw new Exception('Formulae with more than two array arguments are not supported');
}
}
public function arguments(): array
{
return $this->arguments;
}
public function hasArrayArgument(): bool
{
return $this->arrayArguments() > 0;
}
public function getFirstArrayArgumentNumber(): int
{
$rowArrays = $this->filterArray($this->rows);
$columnArrays = $this->filterArray($this->columns);
for ($index = $this->indexStart; $index < $this->argumentCount; ++$index) {
if (isset($rowArrays[$index]) || isset($columnArrays[$index])) {
return ++$index;
}
}
return 0;
}
public function getSingleRowVector(): ?int
{
$rowVectors = $this->getRowVectors();
return count($rowVectors) === 1 ? array_pop($rowVectors) : null;
}
private function getRowVectors(): array
{
$rowVectors = [];
for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) {
if ($this->rows[$index] === 1 && $this->columns[$index] > 1) {
$rowVectors[] = $index;
}
}
return $rowVectors;
}
public function getSingleColumnVector(): ?int
{
$columnVectors = $this->getColumnVectors();
return count($columnVectors) === 1 ? array_pop($columnVectors) : null;
}
private function getColumnVectors(): array
{
$columnVectors = [];
for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) {
if ($this->rows[$index] > 1 && $this->columns[$index] === 1) {
$columnVectors[] = $index;
}
}
return $columnVectors;
}
public function getMatrixPair(): array
{
for ($i = $this->indexStart; $i < ($this->indexStart + $this->argumentCount - 1); ++$i) {
for ($j = $i + 1; $j < $this->argumentCount; ++$j) {
if (isset($this->rows[$i], $this->rows[$j])) {
return [$i, $j];
}
}
}
return [];
}
public function isVector(int $argument): bool
{
return $this->rows[$argument] === 1 || $this->columns[$argument] === 1;
}
public function isRowVector(int $argument): bool
{
return $this->rows[$argument] === 1;
}
public function isColumnVector(int $argument): bool
{
return $this->columns[$argument] === 1;
}
public function rowCount(int $argument): int
{
return $this->rows[$argument];
}
public function columnCount(int $argument): int
{
return $this->columns[$argument];
}
private function rows(array $arguments): array
{
return array_map(
fn ($argument): int => is_countable($argument) ? count($argument) : 1,
$arguments
);
}
private function columns(array $arguments): array
{
return array_map(
function (mixed $argument): int {
return is_array($argument) && is_array($argument[array_keys($argument)[0]])
? count($argument[array_keys($argument)[0]])
: 1;
},
$arguments
);
}
public function arrayArguments(): int
{
$count = 0;
foreach (array_keys($this->arguments) as $argument) {
if ($this->rows[$argument] > 1 || $this->columns[$argument] > 1) {
++$count;
}
}
return $count;
}
private function flattenSingleCellArrays(array $arguments, array $rows, array $columns): array
{
foreach ($arguments as $index => $argument) {
if ($rows[$index] === 1 && $columns[$index] === 1) {
while (is_array($argument)) {
$argument = array_pop($argument);
}
$arguments[$index] = $argument;
}
}
return $arguments;
}
private function filterArray(array $array): array
{
return array_filter(
$array,
fn ($value): bool => $value > 1
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Constants.php 0000644 00000001046 15167673465 0022125 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
class Constants
{
public const BASIS_DAYS_PER_YEAR_NASD = 0;
public const BASIS_DAYS_PER_YEAR_ACTUAL = 1;
public const BASIS_DAYS_PER_YEAR_360 = 2;
public const BASIS_DAYS_PER_YEAR_365 = 3;
public const BASIS_DAYS_PER_YEAR_360_EUROPEAN = 4;
public const FREQUENCY_ANNUAL = 1;
public const FREQUENCY_SEMI_ANNUAL = 2;
public const FREQUENCY_QUARTERLY = 4;
public const PAYMENT_END_OF_PERIOD = 0;
public const PAYMENT_BEGINNING_OF_PERIOD = 1;
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php 0000644 00000023113 15167673465 0022556 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Depreciation
{
private static float $zeroPointZero = 0.0;
/**
* DB.
*
* Returns the depreciation of an asset for a specified period using the
* fixed-declining balance method.
* This form of depreciation is used if you want to get a higher depreciation value
* at the beginning of the depreciation (as opposed to linear depreciation). The
* depreciation value is reduced with every depreciation period by the depreciation
* already deducted from the initial cost.
*
* Excel Function:
* DB(cost,salvage,life,period[,month])
*
* @param mixed $cost Initial cost of the asset
* @param mixed $salvage Value at the end of the depreciation.
* (Sometimes called the salvage value of the asset)
* @param mixed $life Number of periods over which the asset is depreciated.
* (Sometimes called the useful life of the asset)
* @param mixed $period The period for which you want to calculate the
* depreciation. Period must use the same units as life.
* @param mixed $month Number of months in the first year. If month is omitted,
* it defaults to 12.
*/
public static function DB(mixed $cost, mixed $salvage, mixed $life, mixed $period, mixed $month = 12): string|float|int
{
$cost = Functions::flattenSingleValue($cost);
$salvage = Functions::flattenSingleValue($salvage);
$life = Functions::flattenSingleValue($life);
$period = Functions::flattenSingleValue($period);
$month = Functions::flattenSingleValue($month);
try {
$cost = self::validateCost($cost);
$salvage = self::validateSalvage($salvage);
$life = self::validateLife($life);
$period = self::validatePeriod($period);
$month = self::validateMonth($month);
} catch (Exception $e) {
return $e->getMessage();
}
if ($cost === self::$zeroPointZero) {
return 0.0;
}
// Set Fixed Depreciation Rate
$fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life);
$fixedDepreciationRate = round($fixedDepreciationRate, 3);
// Loop through each period calculating the depreciation
// TODO Handle period value between 0 and 1 (e.g. 0.5)
$previousDepreciation = 0;
$depreciation = 0;
for ($per = 1; $per <= $period; ++$per) {
if ($per == 1) {
$depreciation = $cost * $fixedDepreciationRate * $month / 12;
} elseif ($per == ($life + 1)) {
$depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12;
} else {
$depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate;
}
$previousDepreciation += $depreciation;
}
return $depreciation;
}
/**
* DDB.
*
* Returns the depreciation of an asset for a specified period using the
* double-declining balance method or some other method you specify.
*
* Excel Function:
* DDB(cost,salvage,life,period[,factor])
*
* @param mixed $cost Initial cost of the asset
* @param mixed $salvage Value at the end of the depreciation.
* (Sometimes called the salvage value of the asset)
* @param mixed $life Number of periods over which the asset is depreciated.
* (Sometimes called the useful life of the asset)
* @param mixed $period The period for which you want to calculate the
* depreciation. Period must use the same units as life.
* @param mixed $factor The rate at which the balance declines.
* If factor is omitted, it is assumed to be 2 (the
* double-declining balance method).
*/
public static function DDB(mixed $cost, mixed $salvage, mixed $life, mixed $period, mixed $factor = 2.0): float|string
{
$cost = Functions::flattenSingleValue($cost);
$salvage = Functions::flattenSingleValue($salvage);
$life = Functions::flattenSingleValue($life);
$period = Functions::flattenSingleValue($period);
$factor = Functions::flattenSingleValue($factor);
try {
$cost = self::validateCost($cost);
$salvage = self::validateSalvage($salvage);
$life = self::validateLife($life);
$period = self::validatePeriod($period);
$factor = self::validateFactor($factor);
} catch (Exception $e) {
return $e->getMessage();
}
if ($period > $life) {
return ExcelError::NAN();
}
// Loop through each period calculating the depreciation
// TODO Handling for fractional $period values
$previousDepreciation = 0;
$depreciation = 0;
for ($per = 1; $per <= $period; ++$per) {
$depreciation = min(
($cost - $previousDepreciation) * ($factor / $life),
($cost - $salvage - $previousDepreciation)
);
$previousDepreciation += $depreciation;
}
return $depreciation;
}
/**
* SLN.
*
* Returns the straight-line depreciation of an asset for one period
*
* @param mixed $cost Initial cost of the asset
* @param mixed $salvage Value at the end of the depreciation
* @param mixed $life Number of periods over which the asset is depreciated
*
* @return float|string Result, or a string containing an error
*/
public static function SLN(mixed $cost, mixed $salvage, mixed $life): string|float
{
$cost = Functions::flattenSingleValue($cost);
$salvage = Functions::flattenSingleValue($salvage);
$life = Functions::flattenSingleValue($life);
try {
$cost = self::validateCost($cost, true);
$salvage = self::validateSalvage($salvage, true);
$life = self::validateLife($life, true);
} catch (Exception $e) {
return $e->getMessage();
}
if ($life === self::$zeroPointZero) {
return ExcelError::DIV0();
}
return ($cost - $salvage) / $life;
}
/**
* SYD.
*
* Returns the sum-of-years' digits depreciation of an asset for a specified period.
*
* @param mixed $cost Initial cost of the asset
* @param mixed $salvage Value at the end of the depreciation
* @param mixed $life Number of periods over which the asset is depreciated
* @param mixed $period Period
*
* @return float|string Result, or a string containing an error
*/
public static function SYD(mixed $cost, mixed $salvage, mixed $life, mixed $period): string|float
{
$cost = Functions::flattenSingleValue($cost);
$salvage = Functions::flattenSingleValue($salvage);
$life = Functions::flattenSingleValue($life);
$period = Functions::flattenSingleValue($period);
try {
$cost = self::validateCost($cost, true);
$salvage = self::validateSalvage($salvage);
$life = self::validateLife($life);
$period = self::validatePeriod($period);
} catch (Exception $e) {
return $e->getMessage();
}
if ($period > $life) {
return ExcelError::NAN();
}
$syd = (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
return $syd;
}
private static function validateCost(mixed $cost, bool $negativeValueAllowed = false): float
{
$cost = FinancialValidations::validateFloat($cost);
if ($cost < 0.0 && $negativeValueAllowed === false) {
throw new Exception(ExcelError::NAN());
}
return $cost;
}
private static function validateSalvage(mixed $salvage, bool $negativeValueAllowed = false): float
{
$salvage = FinancialValidations::validateFloat($salvage);
if ($salvage < 0.0 && $negativeValueAllowed === false) {
throw new Exception(ExcelError::NAN());
}
return $salvage;
}
private static function validateLife(mixed $life, bool $negativeValueAllowed = false): float
{
$life = FinancialValidations::validateFloat($life);
if ($life < 0.0 && $negativeValueAllowed === false) {
throw new Exception(ExcelError::NAN());
}
return $life;
}
private static function validatePeriod(mixed $period, bool $negativeValueAllowed = false): float
{
$period = FinancialValidations::validateFloat($period);
if ($period <= 0.0 && $negativeValueAllowed === false) {
throw new Exception(ExcelError::NAN());
}
return $period;
}
private static function validateMonth(mixed $month): int
{
$month = FinancialValidations::validateInt($month);
if ($month < 1) {
throw new Exception(ExcelError::NAN());
}
return $month;
}
private static function validateFactor(mixed $factor): float
{
$factor = FinancialValidations::validateFloat($factor);
if ($factor <= 0.0) {
throw new Exception(ExcelError::NAN());
}
return $factor;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php 0000644 00000011113 15167673465 0021362 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\TextData\Format;
class Dollar
{
use ArrayEnabled;
/**
* DOLLAR.
*
* This function converts a number to text using currency format, with the decimals rounded to the specified place.
* The format used is $#,##0.00_);($#,##0.00)..
*
* @param mixed $number The value to format, or can be an array of numbers
* Or can be an array of values
* @param mixed $precision The number of digits to display to the right of the decimal point (as an integer).
* If precision is negative, number is rounded to the left of the decimal point.
* If you omit precision, it is assumed to be 2
* Or can be an array of precision values
*
* @return array|string If an array of values is passed for either of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function format(mixed $number, mixed $precision = 2)
{
return Format::DOLLAR($number, $precision);
}
/**
* DOLLARDE.
*
* Converts a dollar price expressed as an integer part and a fraction
* part into a dollar price expressed as a decimal number.
* Fractional dollar numbers are sometimes used for security prices.
*
* Excel Function:
* DOLLARDE(fractional_dollar,fraction)
*
* @param mixed $fractionalDollar Fractional Dollar
* Or can be an array of values
* @param mixed $fraction Fraction
* Or can be an array of values
*/
public static function decimal(mixed $fractionalDollar = null, mixed $fraction = 0): array|string|float
{
if (is_array($fractionalDollar) || is_array($fraction)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $fractionalDollar, $fraction);
}
try {
$fractionalDollar = FinancialValidations::validateFloat(
Functions::flattenSingleValue($fractionalDollar) ?? 0.0
);
$fraction = FinancialValidations::validateInt(Functions::flattenSingleValue($fraction));
} catch (Exception $e) {
return $e->getMessage();
}
// Additional parameter validations
if ($fraction < 0) {
return ExcelError::NAN();
}
if ($fraction == 0) {
return ExcelError::DIV0();
}
$dollars = ($fractionalDollar < 0) ? ceil($fractionalDollar) : floor($fractionalDollar);
$cents = fmod($fractionalDollar, 1.0);
$cents /= $fraction;
$cents *= 10 ** ceil(log10($fraction));
return $dollars + $cents;
}
/**
* DOLLARFR.
*
* Converts a dollar price expressed as a decimal number into a dollar price
* expressed as a fraction.
* Fractional dollar numbers are sometimes used for security prices.
*
* Excel Function:
* DOLLARFR(decimal_dollar,fraction)
*
* @param mixed $decimalDollar Decimal Dollar
* Or can be an array of values
* @param mixed $fraction Fraction
* Or can be an array of values
*/
public static function fractional(mixed $decimalDollar = null, mixed $fraction = 0): array|string|float
{
if (is_array($decimalDollar) || is_array($fraction)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $decimalDollar, $fraction);
}
try {
$decimalDollar = FinancialValidations::validateFloat(
Functions::flattenSingleValue($decimalDollar) ?? 0.0
);
$fraction = FinancialValidations::validateInt(Functions::flattenSingleValue($fraction));
} catch (Exception $e) {
return $e->getMessage();
}
// Additional parameter validations
if ($fraction < 0) {
return ExcelError::NAN();
}
if ($fraction == 0) {
return ExcelError::DIV0();
}
$dollars = ($decimalDollar < 0.0) ? ceil($decimalDollar) : floor($decimalDollar);
$cents = fmod($decimalDollar, 1);
$cents *= $fraction;
$cents *= 10 ** (-ceil(log10($fraction)));
return $dollars + $cents;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php 0000644 00000006147 15167673465 0024242 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class FinancialValidations
{
public static function validateDate(mixed $date): float
{
return DateTimeExcel\Helpers::getDateValue($date);
}
public static function validateSettlementDate(mixed $settlement): float
{
return self::validateDate($settlement);
}
public static function validateMaturityDate(mixed $maturity): float
{
return self::validateDate($maturity);
}
public static function validateFloat(mixed $value): float
{
if (!is_numeric($value)) {
throw new Exception(ExcelError::VALUE());
}
return (float) $value;
}
public static function validateInt(mixed $value): int
{
if (!is_numeric($value)) {
throw new Exception(ExcelError::VALUE());
}
return (int) floor((float) $value);
}
public static function validateRate(mixed $rate): float
{
$rate = self::validateFloat($rate);
if ($rate < 0.0) {
throw new Exception(ExcelError::NAN());
}
return $rate;
}
public static function validateFrequency(mixed $frequency): int
{
$frequency = self::validateInt($frequency);
if (
($frequency !== FinancialConstants::FREQUENCY_ANNUAL)
&& ($frequency !== FinancialConstants::FREQUENCY_SEMI_ANNUAL)
&& ($frequency !== FinancialConstants::FREQUENCY_QUARTERLY)
) {
throw new Exception(ExcelError::NAN());
}
return $frequency;
}
public static function validateBasis(mixed $basis): int
{
if (!is_numeric($basis)) {
throw new Exception(ExcelError::VALUE());
}
$basis = (int) $basis;
if (($basis < 0) || ($basis > 4)) {
throw new Exception(ExcelError::NAN());
}
return $basis;
}
public static function validatePrice(mixed $price): float
{
$price = self::validateFloat($price);
if ($price < 0.0) {
throw new Exception(ExcelError::NAN());
}
return $price;
}
public static function validateParValue(mixed $parValue): float
{
$parValue = self::validateFloat($parValue);
if ($parValue < 0.0) {
throw new Exception(ExcelError::NAN());
}
return $parValue;
}
public static function validateYield(mixed $yield): float
{
$yield = self::validateFloat($yield);
if ($yield < 0.0) {
throw new Exception(ExcelError::NAN());
}
return $yield;
}
public static function validateDiscount(mixed $discount): float
{
$discount = self::validateFloat($discount);
if ($discount <= 0.0) {
throw new Exception(ExcelError::NAN());
}
return $discount;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php 0000644 00000012615 15167673465 0025146 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Periodic
{
const FINANCIAL_MAX_ITERATIONS = 128;
const FINANCIAL_PRECISION = 1.0e-08;
/**
* IRR.
*
* Returns the internal rate of return for a series of cash flows represented by the numbers in values.
* These cash flows do not have to be even, as they would be for an annuity. However, the cash flows must occur
* at regular intervals, such as monthly or annually. The internal rate of return is the interest rate received
* for an investment consisting of payments (negative values) and income (positive values) that occur at regular
* periods.
*
* Excel Function:
* IRR(values[,guess])
*
* @param mixed $values An array or a reference to cells that contain numbers for which you want
* to calculate the internal rate of return.
* Values must contain at least one positive value and one negative value to
* calculate the internal rate of return.
* @param mixed $guess A number that you guess is close to the result of IRR
*/
public static function rate(mixed $values, mixed $guess = 0.1): string|float
{
if (!is_array($values)) {
return ExcelError::VALUE();
}
$values = Functions::flattenArray($values);
$guess = Functions::flattenSingleValue($guess);
// create an initial range, with a root somewhere between 0 and guess
$x1 = 0.0;
$x2 = $guess;
$f1 = self::presentValue($x1, $values);
$f2 = self::presentValue($x2, $values);
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
if (($f1 * $f2) < 0.0) {
break;
}
if (abs($f1) < abs($f2)) {
$f1 = self::presentValue($x1 += 1.6 * ($x1 - $x2), $values);
} else {
$f2 = self::presentValue($x2 += 1.6 * ($x2 - $x1), $values);
}
}
if (($f1 * $f2) > 0.0) {
return ExcelError::VALUE();
}
$f = self::presentValue($x1, $values);
if ($f < 0.0) {
$rtb = $x1;
$dx = $x2 - $x1;
} else {
$rtb = $x2;
$dx = $x1 - $x2;
}
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
$dx *= 0.5;
$x_mid = $rtb + $dx;
$f_mid = self::presentValue($x_mid, $values);
if ($f_mid <= 0.0) {
$rtb = $x_mid;
}
if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
return $x_mid;
}
}
return ExcelError::VALUE();
}
/**
* MIRR.
*
* Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both
* the cost of the investment and the interest received on reinvestment of cash.
*
* Excel Function:
* MIRR(values,finance_rate, reinvestment_rate)
*
* @param mixed $values An array or a reference to cells that contain a series of payments and
* income occurring at regular intervals.
* Payments are negative value, income is positive values.
* @param mixed $financeRate The interest rate you pay on the money used in the cash flows
* @param mixed $reinvestmentRate The interest rate you receive on the cash flows as you reinvest them
*
* @return float|string Result, or a string containing an error
*/
public static function modifiedRate(mixed $values, mixed $financeRate, mixed $reinvestmentRate): string|float
{
if (!is_array($values)) {
return ExcelError::DIV0();
}
$values = Functions::flattenArray($values);
$financeRate = Functions::flattenSingleValue($financeRate);
$reinvestmentRate = Functions::flattenSingleValue($reinvestmentRate);
$n = count($values);
$rr = 1.0 + $reinvestmentRate;
$fr = 1.0 + $financeRate;
$npvPos = $npvNeg = 0.0;
foreach ($values as $i => $v) {
if ($v >= 0) {
$npvPos += $v / $rr ** $i;
} else {
$npvNeg += $v / $fr ** $i;
}
}
if ($npvNeg === 0.0 || $npvPos === 0.0) {
return ExcelError::DIV0();
}
$mirr = ((-$npvPos * $rr ** $n)
/ ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0;
return is_finite($mirr) ? $mirr : ExcelError::NAN();
}
/**
* NPV.
*
* Returns the Net Present Value of a cash flow series given a discount rate.
*
* @param array $args
*/
public static function presentValue(mixed $rate, ...$args): int|float
{
$returnValue = 0;
$rate = Functions::flattenSingleValue($rate);
$aArgs = Functions::flattenArray($args);
// Calculate
$countArgs = count($aArgs);
for ($i = 1; $i <= $countArgs; ++$i) {
// Is it a numeric value?
if (is_numeric($aArgs[$i - 1])) {
$returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i;
}
}
return $returnValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php 0000644 00000024670 15167673465 0025625 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class NonPeriodic
{
const FINANCIAL_MAX_ITERATIONS = 128;
const FINANCIAL_PRECISION = 1.0e-08;
const DEFAULT_GUESS = 0.1;
/**
* XIRR.
*
* Returns the internal rate of return for a schedule of cash flows that is not necessarily periodic.
*
* Excel Function:
* =XIRR(values,dates,guess)
*
* @param float[] $values A series of cash flow payments
* The series of values must contain at least one positive value & one negative value
* @param mixed[] $dates A series of payment dates
* The first payment date indicates the beginning of the schedule of payments
* All other dates must be later than this date, but they may occur in any order
* @param mixed $guess An optional guess at the expected answer
*/
public static function rate(array $values, array $dates, mixed $guess = self::DEFAULT_GUESS): float|string
{
$rslt = self::xirrPart1($values, $dates);
if ($rslt !== '') {
return $rslt;
}
// create an initial range, with a root somewhere between 0 and guess
$guess = Functions::flattenSingleValue($guess) ?? self::DEFAULT_GUESS;
if (!is_numeric($guess)) {
return ExcelError::VALUE();
}
$guess = ($guess + 0.0) ?: self::DEFAULT_GUESS;
$x1 = 0.0;
$x2 = $guess + 0.0;
$f1 = self::xnpvOrdered($x1, $values, $dates, false);
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
$found = false;
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
if (!is_numeric($f1)) {
return $f1;
}
if (!is_numeric($f2)) {
return $f2;
}
$f1 = (float) $f1;
$f2 = (float) $f2;
if (($f1 * $f2) < 0.0) {
$found = true;
break;
} elseif (abs($f1) < abs($f2)) {
$x1 += 1.6 * ($x1 - $x2);
$f1 = self::xnpvOrdered($x1, $values, $dates, false);
} else {
$x2 += 1.6 * ($x2 - $x1);
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
}
}
if ($found) {
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();
}
/**
* XNPV.
*
* Returns the net present value for a schedule of cash flows that is not necessarily periodic.
* To calculate the net present value for a series of cash flows that is periodic, use the NPV function.
*
* Excel Function:
* =XNPV(rate,values,dates)
*
* @param array|float $rate the discount rate to apply to the cash flows
* @param float[] $values A series of cash flows that corresponds to a schedule of payments in dates.
* The first payment is optional and corresponds to a cost or payment that occurs
* at the beginning of the investment.
* If the first value is a cost or payment, it must be a negative value.
* All succeeding payments are discounted based on a 365-day year.
* The series of values must contain at least one positive value and one negative value.
* @param mixed[] $dates A schedule of payment dates that corresponds to the cash flow payments.
* The first payment date indicates the beginning of the schedule of payments.
* All other dates must be later than this date, but they may occur in any order.
*/
public static function presentValue(array|float $rate, array $values, array $dates): float|string
{
return self::xnpvOrdered($rate, $values, $dates, true);
}
private static function bothNegAndPos(bool $neg, bool $pos): bool
{
return $neg && $pos;
}
private static function xirrPart1(mixed &$values, mixed &$dates): string
{
$values = Functions::flattenArray($values);
$dates = Functions::flattenArray($dates);
$valuesIsArray = count($values) > 1;
$datesIsArray = count($dates) > 1;
if (!$valuesIsArray && !$datesIsArray) {
return ExcelError::NA();
}
if (count($values) != count($dates)) {
return ExcelError::NAN();
}
$datesCount = count($dates);
for ($i = 0; $i < $datesCount; ++$i) {
try {
$dates[$i] = DateTimeExcel\Helpers::getDateValue($dates[$i]);
} catch (Exception $e) {
return $e->getMessage();
}
}
return self::xirrPart2($values);
}
private static function xirrPart2(array &$values): string
{
$valCount = count($values);
$foundpos = false;
$foundneg = false;
for ($i = 0; $i < $valCount; ++$i) {
$fld = $values[$i];
if (!is_numeric($fld)) {
return ExcelError::VALUE();
} elseif ($fld > 0) {
$foundpos = true;
} elseif ($fld < 0) {
$foundneg = true;
}
}
if (!self::bothNegAndPos($foundneg, $foundpos)) {
return ExcelError::NAN();
}
return '';
}
private static function xirrPart3(array $values, array $dates, float $x1, float $x2): float|string
{
$f = self::xnpvOrdered($x1, $values, $dates, false);
if ($f < 0.0) {
$rtb = $x1;
$dx = $x2 - $x1;
} else {
$rtb = $x2;
$dx = $x1 - $x2;
}
$rslt = ExcelError::VALUE();
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
$dx *= 0.5;
$x_mid = $rtb + $dx;
$f_mid = (float) self::xnpvOrdered($x_mid, $values, $dates, false);
if ($f_mid <= 0.0) {
$rtb = $x_mid;
}
if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
$rslt = $x_mid;
break;
}
}
return $rslt;
}
private static function xirrBisection(array $values, array $dates, float $x1, float $x2): string|float
{
$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;
}
private static function xnpvOrdered(mixed $rate, mixed $values, mixed $dates, bool $ordered = true, bool $capAtNegative1 = false): float|string
{
$rate = Functions::flattenSingleValue($rate);
$values = Functions::flattenArray($values);
$dates = Functions::flattenArray($dates);
$valCount = count($values);
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();
}
$xnpv = 0.0;
for ($i = 0; $i < $valCount; ++$i) {
if (!is_numeric($values[$i])) {
return ExcelError::VALUE();
}
try {
$datei = DateTimeExcel\Helpers::getDateValue($dates[$i]);
} catch (Exception $e) {
return $e->getMessage();
}
if ($date0 > $datei) {
$dif = $ordered ? ExcelError::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd'));
} else {
$dif = Functions::scalar(DateTimeExcel\Difference::interval($date0, $datei, 'd'));
}
if (!is_numeric($dif)) {
return $dif;
}
if ($rate <= -1.0) {
$xnpv += -abs($values[$i]) / (-1 - $rate) ** ($dif / 365);
} else {
$xnpv += $values[$i] / (1 + $rate) ** ($dif / 365);
}
}
return is_finite($xnpv) ? $xnpv : ExcelError::VALUE();
}
private static function validateXnpv(mixed $rate, array $values, array $dates): void
{
if (!is_numeric($rate)) {
throw new Exception(ExcelError::VALUE());
}
$valCount = count($values);
if ($valCount != count($dates)) {
throw new Exception(ExcelError::NAN());
}
if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) {
throw new Exception(ExcelError::NAN());
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php 0000644 00000002247 15167673465 0025567 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\FinancialValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class CashFlowValidations extends FinancialValidations
{
public static function validateRate(mixed $rate): float
{
$rate = self::validateFloat($rate);
return $rate;
}
public static function validatePeriodType(mixed $type): int
{
$rate = self::validateInt($type);
if (
$type !== FinancialConstants::PAYMENT_END_OF_PERIOD
&& $type !== FinancialConstants::PAYMENT_BEGINNING_OF_PERIOD
) {
throw new Exception(ExcelError::NAN());
}
return $rate;
}
public static function validatePresentValue(mixed $presentValue): float
{
return self::validateFloat($presentValue);
}
public static function validateFutureValue(mixed $futureValue): float
{
return self::validateFloat($futureValue);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php 0000644 00000007422 15167673465 0023104 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Single
{
/**
* FVSCHEDULE.
*
* Returns the future value of an initial principal after applying a series of compound interest rates.
* Use FVSCHEDULE to calculate the future value of an investment with a variable or adjustable rate.
*
* Excel Function:
* FVSCHEDULE(principal,schedule)
*
* @param mixed $principal the present value
* @param float[] $schedule an array of interest rates to apply
*/
public static function futureValue(mixed $principal, array $schedule): string|float
{
$principal = Functions::flattenSingleValue($principal);
$schedule = Functions::flattenArray($schedule);
try {
$principal = CashFlowValidations::validateFloat($principal);
foreach ($schedule as $rate) {
$rate = CashFlowValidations::validateFloat($rate);
$principal *= 1 + $rate;
}
} catch (Exception $e) {
return $e->getMessage();
}
return $principal;
}
/**
* PDURATION.
*
* Calculates the number of periods required for an investment to reach a specified value.
*
* @param mixed $rate Interest rate per period
* @param mixed $presentValue Present Value
* @param mixed $futureValue Future Value
*
* @return float|string Result, or a string containing an error
*/
public static function periods(mixed $rate, mixed $presentValue, mixed $futureValue): string|float
{
$rate = Functions::flattenSingleValue($rate);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = Functions::flattenSingleValue($futureValue);
try {
$rate = CashFlowValidations::validateRate($rate);
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
} catch (Exception $e) {
return $e->getMessage();
}
// Validate parameters
if ($rate <= 0.0 || $presentValue <= 0.0 || $futureValue <= 0.0) {
return ExcelError::NAN();
}
return (log($futureValue) - log($presentValue)) / log(1 + $rate);
}
/**
* RRI.
*
* Calculates the interest rate required for an investment to grow to a specified future value .
*
* @param array|float $periods The number of periods over which the investment is made
* @param array|float $presentValue Present Value
* @param array|float $futureValue Future Value
*
* @return float|string Result, or a string containing an error
*/
public static function interestRate(array|float $periods = 0.0, array|float $presentValue = 0.0, array|float $futureValue = 0.0): string|float
{
$periods = Functions::flattenSingleValue($periods);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = Functions::flattenSingleValue($futureValue);
try {
$periods = CashFlowValidations::validateFloat($periods);
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
} catch (Exception $e) {
return $e->getMessage();
}
// Validate parameters
if ($periods <= 0.0 || $presentValue <= 0.0 || $futureValue < 0.0) {
return ExcelError::NAN();
}
return ($futureValue / $presentValue) ** (1 / $periods) - 1;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.php 0000644 00000017433 15167673465 0025215 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Periodic
{
/**
* FV.
*
* Returns the Future Value of a cash flow with constant payments and interest rate (annuities).
*
* Excel Function:
* FV(rate,nper,pmt[,pv[,type]])
*
* @param mixed $rate The interest rate per period
* @param mixed $numberOfPeriods Total number of payment periods in an annuity as an integer
* @param mixed $payment The payment made each period: it cannot change over the
* life of the annuity. Typically, pmt contains principal
* and interest but no other fees or taxes.
* @param mixed $presentValue present Value, or the lump-sum amount that a series of
* future payments is worth right now
* @param mixed $type A number 0 or 1 and indicates when payments are due:
* 0 or omitted At the end of the period.
* 1 At the beginning of the period.
*/
public static function futureValue(
mixed $rate,
mixed $numberOfPeriods,
mixed $payment = 0.0,
mixed $presentValue = 0.0,
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
): string|float {
$rate = Functions::flattenSingleValue($rate);
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment);
$presentValue = ($presentValue === null) ? 0.0 : Functions::flattenSingleValue($presentValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
try {
$rate = CashFlowValidations::validateRate($rate);
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
$payment = CashFlowValidations::validateFloat($payment);
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
$type = CashFlowValidations::validatePeriodType($type);
} catch (Exception $e) {
return $e->getMessage();
}
return self::calculateFutureValue($rate, $numberOfPeriods, $payment, $presentValue, $type);
}
/**
* PV.
*
* Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
*
* @param mixed $rate Interest rate per period
* @param mixed $numberOfPeriods Number of periods as an integer
* @param mixed $payment Periodic payment (annuity)
* @param mixed $futureValue Future Value
* @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
*
* @return float|string Result, or a string containing an error
*/
public static function presentValue(
mixed $rate,
mixed $numberOfPeriods,
mixed $payment = 0.0,
mixed $futureValue = 0.0,
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
): string|float {
$rate = Functions::flattenSingleValue($rate);
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
try {
$rate = CashFlowValidations::validateRate($rate);
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
$payment = CashFlowValidations::validateFloat($payment);
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
$type = CashFlowValidations::validatePeriodType($type);
} catch (Exception $e) {
return $e->getMessage();
}
// Validate parameters
if ($numberOfPeriods < 0) {
return ExcelError::NAN();
}
return self::calculatePresentValue($rate, $numberOfPeriods, $payment, $futureValue, $type);
}
/**
* NPER.
*
* Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate.
*
* @param mixed $rate Interest rate per period
* @param mixed $payment Periodic payment (annuity)
* @param mixed $presentValue Present Value
* @param mixed $futureValue Future Value
* @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
*
* @return float|string Result, or a string containing an error
*/
public static function periods(
mixed $rate,
mixed $payment,
mixed $presentValue,
mixed $futureValue = 0.0,
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
) {
$rate = Functions::flattenSingleValue($rate);
$payment = Functions::flattenSingleValue($payment);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
try {
$rate = CashFlowValidations::validateRate($rate);
$payment = CashFlowValidations::validateFloat($payment);
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
$type = CashFlowValidations::validatePeriodType($type);
} catch (Exception $e) {
return $e->getMessage();
}
// Validate parameters
if ($payment == 0.0) {
return ExcelError::NAN();
}
return self::calculatePeriods($rate, $payment, $presentValue, $futureValue, $type);
}
private static function calculateFutureValue(
float $rate,
int $numberOfPeriods,
float $payment,
float $presentValue,
int $type
): float {
if ($rate !== null && $rate != 0) {
return -$presentValue
* (1 + $rate) ** $numberOfPeriods - $payment * (1 + $rate * $type) * ((1 + $rate) ** $numberOfPeriods - 1)
/ $rate;
}
return -$presentValue - $payment * $numberOfPeriods;
}
private static function calculatePresentValue(
float $rate,
int $numberOfPeriods,
float $payment,
float $futureValue,
int $type
): float {
if ($rate != 0.0) {
return (-$payment * (1 + $rate * $type)
* (((1 + $rate) ** $numberOfPeriods - 1) / $rate) - $futureValue) / (1 + $rate) ** $numberOfPeriods;
}
return -$futureValue - $payment * $numberOfPeriods;
}
private static function calculatePeriods(
float $rate,
float $payment,
float $presentValue,
float $futureValue,
int $type
): string|float {
if ($rate != 0.0) {
if ($presentValue == 0.0) {
return ExcelError::NAN();
}
return log(($payment * (1 + $rate * $type) / $rate - $futureValue)
/ ($presentValue + $payment * (1 + $rate * $type) / $rate)) / log(1 + $rate);
}
return (-$presentValue - $futureValue) / $payment;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php 0000644 00000022237 15167673465 0027010 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Interest
{
private const FINANCIAL_MAX_ITERATIONS = 128;
private const FINANCIAL_PRECISION = 1.0e-08;
/**
* IPMT.
*
* Returns the interest payment for a given period for an investment based on periodic, constant payments
* and a constant interest rate.
*
* Excel Function:
* IPMT(rate,per,nper,pv[,fv][,type])
*
* @param mixed $interestRate Interest rate per period
* @param mixed $period Period for which we want to find the interest
* @param mixed $numberOfPeriods Number of periods
* @param mixed $presentValue Present Value
* @param mixed $futureValue Future Value
* @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
*/
public static function payment(
mixed $interestRate,
mixed $period,
mixed $numberOfPeriods,
mixed $presentValue,
mixed $futureValue = 0,
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
): string|float {
$interestRate = Functions::flattenSingleValue($interestRate);
$period = Functions::flattenSingleValue($period);
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
try {
$interestRate = CashFlowValidations::validateRate($interestRate);
$period = CashFlowValidations::validateInt($period);
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
$type = CashFlowValidations::validatePeriodType($type);
} catch (Exception $e) {
return $e->getMessage();
}
// Validate parameters
if ($period <= 0 || $period > $numberOfPeriods) {
return ExcelError::NAN();
}
// Calculate
$interestAndPrincipal = new InterestAndPrincipal(
$interestRate,
$period,
$numberOfPeriods,
$presentValue,
$futureValue,
$type
);
return $interestAndPrincipal->interest();
}
/**
* ISPMT.
*
* Returns the interest payment for an investment based on an interest rate and a constant payment schedule.
*
* Excel Function:
* =ISPMT(interest_rate, period, number_payments, pv)
*
* @param mixed $interestRate is the interest rate for the investment
* @param mixed $period is the period to calculate the interest rate. It must be betweeen 1 and number_payments.
* @param mixed $numberOfPeriods is the number of payments for the annuity
* @param mixed $principleRemaining is the loan amount or present value of the payments
*/
public static function schedulePayment(mixed $interestRate, mixed $period, mixed $numberOfPeriods, mixed $principleRemaining): string|float
{
$interestRate = Functions::flattenSingleValue($interestRate);
$period = Functions::flattenSingleValue($period);
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$principleRemaining = Functions::flattenSingleValue($principleRemaining);
try {
$interestRate = CashFlowValidations::validateRate($interestRate);
$period = CashFlowValidations::validateInt($period);
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
$principleRemaining = CashFlowValidations::validateFloat($principleRemaining);
} catch (Exception $e) {
return $e->getMessage();
}
// Validate parameters
if ($period <= 0 || $period > $numberOfPeriods) {
return ExcelError::NAN();
}
// Return value
$returnValue = 0;
// Calculate
$principlePayment = ($principleRemaining * 1.0) / ($numberOfPeriods * 1.0);
for ($i = 0; $i <= $period; ++$i) {
$returnValue = $interestRate * $principleRemaining * -1;
$principleRemaining -= $principlePayment;
// principle needs to be 0 after the last payment, don't let floating point screw it up
if ($i == $numberOfPeriods) {
$returnValue = 0.0;
}
}
return $returnValue;
}
/**
* RATE.
*
* Returns the interest rate per period of an annuity.
* RATE is calculated by iteration and can have zero or more solutions.
* If the successive results of RATE do not converge to within 0.0000001 after 20 iterations,
* RATE returns the #NUM! error value.
*
* Excel Function:
* RATE(nper,pmt,pv[,fv[,type[,guess]]])
*
* @param mixed $numberOfPeriods The total number of payment periods in an annuity
* @param mixed $payment The payment made each period and cannot change over the life of the annuity.
* Typically, pmt includes principal and interest but no other fees or taxes.
* @param mixed $presentValue The present value - the total amount that a series of future payments is worth now
* @param mixed $futureValue The future value, or a cash balance you want to attain after the last payment is made.
* If fv is omitted, it is assumed to be 0 (the future value of a loan,
* for example, is 0).
* @param mixed $type A number 0 or 1 and indicates when payments are due:
* 0 or omitted At the end of the period.
* 1 At the beginning of the period.
* @param mixed $guess Your guess for what the rate will be.
* If you omit guess, it is assumed to be 10 percent.
*/
public static function rate(
mixed $numberOfPeriods,
mixed $payment,
mixed $presentValue,
mixed $futureValue = 0.0,
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD,
mixed $guess = 0.1
): string|float {
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$payment = Functions::flattenSingleValue($payment);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
$guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess);
try {
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
$payment = CashFlowValidations::validateFloat($payment);
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
$type = CashFlowValidations::validatePeriodType($type);
$guess = CashFlowValidations::validateFloat($guess);
} catch (Exception $e) {
return $e->getMessage();
}
$rate = $guess;
// rest of code adapted from python/numpy
$close = false;
$iter = 0;
while (!$close && $iter < self::FINANCIAL_MAX_ITERATIONS) {
$nextdiff = self::rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type);
if (!is_numeric($nextdiff)) {
break;
}
$rate1 = $rate - $nextdiff;
$close = abs($rate1 - $rate) < self::FINANCIAL_PRECISION;
++$iter;
$rate = $rate1;
}
return $close ? $rate : ExcelError::NAN();
}
private static function rateNextGuess(float $rate, int $numberOfPeriods, float $payment, float $presentValue, float $futureValue, int $type): string|float
{
if ($rate == 0.0) {
return ExcelError::NAN();
}
$tt1 = ($rate + 1) ** $numberOfPeriods;
$tt2 = ($rate + 1) ** ($numberOfPeriods - 1);
$numerator = $futureValue + $tt1 * $presentValue + $payment * ($tt1 - 1) * ($rate * $type + 1) / $rate;
$denominator = $numberOfPeriods * $tt2 * $presentValue - $payment * ($tt1 - 1)
* ($rate * $type + 1) / ($rate * $rate) + $numberOfPeriods
* $payment * $tt2 * ($rate * $type + 1) / $rate + $payment * ($tt1 - 1) * $type / $rate;
if ($denominator == 0) {
return ExcelError::NAN();
}
return $numerator / $denominator;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php 0000644 00000011461 15167673465 0027010 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Payments
{
/**
* PMT.
*
* Returns the constant payment (annuity) for a cash flow with a constant interest rate.
*
* @param mixed $interestRate Interest rate per period
* @param mixed $numberOfPeriods Number of periods
* @param mixed $presentValue Present Value
* @param mixed $futureValue Future Value
* @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
*
* @return float|string Result, or a string containing an error
*/
public static function annuity(
mixed $interestRate,
mixed $numberOfPeriods,
mixed $presentValue,
mixed $futureValue = 0,
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
): string|float {
$interestRate = Functions::flattenSingleValue($interestRate);
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
try {
$interestRate = CashFlowValidations::validateRate($interestRate);
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
$type = CashFlowValidations::validatePeriodType($type);
} catch (Exception $e) {
return $e->getMessage();
}
// Calculate
if ($interestRate != 0.0) {
return (-$futureValue - $presentValue * (1 + $interestRate) ** $numberOfPeriods)
/ (1 + $interestRate * $type) / (((1 + $interestRate) ** $numberOfPeriods - 1) / $interestRate);
}
return (-$presentValue - $futureValue) / $numberOfPeriods;
}
/**
* PPMT.
*
* Returns the interest payment for a given period for an investment based on periodic, constant payments
* and a constant interest rate.
*
* @param mixed $interestRate Interest rate per period
* @param mixed $period Period for which we want to find the interest
* @param mixed $numberOfPeriods Number of periods
* @param mixed $presentValue Present Value
* @param mixed $futureValue Future Value
* @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
*
* @return float|string Result, or a string containing an error
*/
public static function interestPayment(
mixed $interestRate,
mixed $period,
mixed $numberOfPeriods,
mixed $presentValue,
mixed $futureValue = 0,
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
): string|float {
$interestRate = Functions::flattenSingleValue($interestRate);
$period = Functions::flattenSingleValue($period);
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
try {
$interestRate = CashFlowValidations::validateRate($interestRate);
$period = CashFlowValidations::validateInt($period);
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
$type = CashFlowValidations::validatePeriodType($type);
} catch (Exception $e) {
return $e->getMessage();
}
// Validate parameters
if ($period <= 0 || $period > $numberOfPeriods) {
return ExcelError::NAN();
}
// Calculate
$interestAndPrincipal = new InterestAndPrincipal(
$interestRate,
$period,
$numberOfPeriods,
$presentValue,
$futureValue,
$type
);
return $interestAndPrincipal->principal();
}
}
src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php 0000644 00000002314 15167673465 0031210 0 ustar 00 phpspreadsheet <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
class InterestAndPrincipal
{
protected float $interest;
protected float $principal;
public function __construct(
float $rate = 0.0,
int $period = 0,
int $numberOfPeriods = 0,
float $presentValue = 0,
float $futureValue = 0,
int $type = FinancialConstants::PAYMENT_END_OF_PERIOD
) {
$payment = Payments::annuity($rate, $numberOfPeriods, $presentValue, $futureValue, $type);
$capital = $presentValue;
$interest = 0.0;
$principal = 0.0;
for ($i = 1; $i <= $period; ++$i) {
$interest = ($type === FinancialConstants::PAYMENT_BEGINNING_OF_PERIOD && $i == 1) ? 0 : -$capital * $rate;
$principal = (float) $payment - $interest;
$capital += $principal;
}
$this->interest = $interest;
$this->principal = $principal;
}
public function interest(): float
{
return $this->interest;
}
public function principal(): float
{
return $this->principal;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php 0000644 00000012267 15167673465 0027333 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Cumulative
{
/**
* CUMIPMT.
*
* Returns the cumulative interest paid on a loan between the start and end periods.
*
* Excel Function:
* CUMIPMT(rate,nper,pv,start,end[,type])
*
* @param mixed $rate The Interest rate
* @param mixed $periods The total number of payment periods
* @param mixed $presentValue Present Value
* @param mixed $start The first period in the calculation.
* Payment periods are numbered beginning with 1.
* @param mixed $end the last period in the calculation
* @param mixed $type A number 0 or 1 and indicates when payments are due:
* 0 or omitted At the end of the period.
* 1 At the beginning of the period.
*/
public static function interest(
mixed $rate,
mixed $periods,
mixed $presentValue,
mixed $start,
mixed $end,
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
): string|float|int {
$rate = Functions::flattenSingleValue($rate);
$periods = Functions::flattenSingleValue($periods);
$presentValue = Functions::flattenSingleValue($presentValue);
$start = Functions::flattenSingleValue($start);
$end = Functions::flattenSingleValue($end);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
try {
$rate = CashFlowValidations::validateRate($rate);
$periods = CashFlowValidations::validateInt($periods);
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
$start = CashFlowValidations::validateInt($start);
$end = CashFlowValidations::validateInt($end);
$type = CashFlowValidations::validatePeriodType($type);
} catch (Exception $e) {
return $e->getMessage();
}
// Validate parameters
if ($start < 1 || $start > $end) {
return ExcelError::NAN();
}
// Calculate
$interest = 0;
for ($per = $start; $per <= $end; ++$per) {
$ipmt = Interest::payment($rate, $per, $periods, $presentValue, 0, $type);
if (is_string($ipmt)) {
return $ipmt;
}
$interest += $ipmt;
}
return $interest;
}
/**
* CUMPRINC.
*
* Returns the cumulative principal paid on a loan between the start and end periods.
*
* Excel Function:
* CUMPRINC(rate,nper,pv,start,end[,type])
*
* @param mixed $rate The Interest rate
* @param mixed $periods The total number of payment periods as an integer
* @param mixed $presentValue Present Value
* @param mixed $start The first period in the calculation.
* Payment periods are numbered beginning with 1.
* @param mixed $end the last period in the calculation
* @param mixed $type A number 0 or 1 and indicates when payments are due:
* 0 or omitted At the end of the period.
* 1 At the beginning of the period.
*/
public static function principal(
mixed $rate,
mixed $periods,
mixed $presentValue,
mixed $start,
mixed $end,
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
): string|float|int {
$rate = Functions::flattenSingleValue($rate);
$periods = Functions::flattenSingleValue($periods);
$presentValue = Functions::flattenSingleValue($presentValue);
$start = Functions::flattenSingleValue($start);
$end = Functions::flattenSingleValue($end);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
try {
$rate = CashFlowValidations::validateRate($rate);
$periods = CashFlowValidations::validateInt($periods);
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
$start = CashFlowValidations::validateInt($start);
$end = CashFlowValidations::validateInt($end);
$type = CashFlowValidations::validatePeriodType($type);
} catch (Exception $e) {
return $e->getMessage();
}
// Validate parameters
if ($start < 1 || $start > $end) {
return ExcelError::VALUE();
}
// Calculate
$principal = 0;
for ($per = $start; $per <= $end; ++$per) {
$ppmt = Payments::interestPayment($rate, $per, $periods, $presentValue, 0, $type);
if (is_string($ppmt)) {
return $ppmt;
}
$principal += $ppmt;
}
return $principal;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php 0000644 00000004010 15167673465 0021545 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use DateTimeInterface;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Helpers
{
/**
* daysPerYear.
*
* Returns the number of days in a specified year, as defined by the "basis" value
*
* @param int|string $year The year against which we're testing
* @param int|string $basis The type of day count:
* 0 or omitted US (NASD) 360
* 1 Actual (365 or 366 in a leap year)
* 2 360
* 3 365
* 4 European 360
*
* @return int|string Result, or a string containing an error
*/
public static function daysPerYear($year, $basis = 0): string|int
{
if (!is_numeric($basis)) {
return ExcelError::NAN();
}
switch ($basis) {
case FinancialConstants::BASIS_DAYS_PER_YEAR_NASD:
case FinancialConstants::BASIS_DAYS_PER_YEAR_360:
case FinancialConstants::BASIS_DAYS_PER_YEAR_360_EUROPEAN:
return 360;
case FinancialConstants::BASIS_DAYS_PER_YEAR_365:
return 365;
case FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL:
return (DateTimeExcel\Helpers::isLeapYear($year)) ? 366 : 365;
}
return ExcelError::NAN();
}
/**
* isLastDayOfMonth.
*
* Returns a boolean TRUE/FALSE indicating if this date is the last date of the month
*
* @param DateTimeInterface $date The date for testing
*/
public static function isLastDayOfMonth(DateTimeInterface $date): bool
{
return $date->format('d') === $date->format('t');
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php 0000644 00000031774 15167673465 0023345 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Price
{
/**
* PRICE.
*
* Returns the price per $100 face value of a security that pays periodic interest.
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue date when the security
* is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $rate the security's annual coupon rate
* @param mixed $yield the security's annual yield
* @param mixed $redemption The number of coupon payments per year.
* For annual payments, frequency = 1;
* for semiannual, frequency = 2;
* for quarterly, frequency = 4.
* @param mixed $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string Result, or a string containing an error
*/
public static function price(
mixed $settlement,
mixed $maturity,
mixed $rate,
mixed $yield,
mixed $redemption,
mixed $frequency,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
): string|float {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$rate = Functions::flattenSingleValue($rate);
$yield = Functions::flattenSingleValue($yield);
$redemption = Functions::flattenSingleValue($redemption);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
$maturity = SecurityValidations::validateMaturityDate($maturity);
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
$rate = SecurityValidations::validateRate($rate);
$yield = SecurityValidations::validateYield($yield);
$redemption = SecurityValidations::validateRedemption($redemption);
$frequency = SecurityValidations::validateFrequency($frequency);
$basis = SecurityValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$dsc = (float) Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis);
$e = (float) Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis);
$n = (int) Coupons::COUPNUM($settlement, $maturity, $frequency, $basis);
$a = (float) Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis);
$baseYF = 1.0 + ($yield / $frequency);
$rfp = 100 * ($rate / $frequency);
$de = $dsc / $e;
$result = $redemption / $baseYF ** (--$n + $de);
for ($k = 0; $k <= $n; ++$k) {
$result += $rfp / ($baseYF ** ($k + $de));
}
$result -= $rfp * ($a / $e);
return $result;
}
/**
* PRICEDISC.
*
* Returns the price per $100 face value of a discounted security.
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue date when the security
* is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $discount The security's discount rate
* @param mixed $redemption The security's redemption value per $100 face value
* @param mixed $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string Result, or a string containing an error
*/
public static function priceDiscounted(
mixed $settlement,
mixed $maturity,
mixed $discount,
mixed $redemption,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
) {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$discount = Functions::flattenSingleValue($discount);
$redemption = Functions::flattenSingleValue($redemption);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
$maturity = SecurityValidations::validateMaturityDate($maturity);
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
$discount = SecurityValidations::validateDiscount($discount);
$redemption = SecurityValidations::validateRedemption($redemption);
$basis = SecurityValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
}
return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity);
}
/**
* PRICEMAT.
*
* Returns the price per $100 face value of a security that pays interest at maturity.
*
* @param mixed $settlement The security's settlement date.
* The security's settlement date is the date after the issue date when the
* security is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $issue The security's issue date
* @param mixed $rate The security's interest rate at date of issue
* @param mixed $yield The security's annual yield
* @param mixed $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string Result, or a string containing an error
*/
public static function priceAtMaturity(
mixed $settlement,
mixed $maturity,
mixed $issue,
mixed $rate,
mixed $yield,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
) {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$issue = Functions::flattenSingleValue($issue);
$rate = Functions::flattenSingleValue($rate);
$yield = Functions::flattenSingleValue($yield);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
$maturity = SecurityValidations::validateMaturityDate($maturity);
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
$issue = SecurityValidations::validateIssueDate($issue);
$rate = SecurityValidations::validateRate($rate);
$yield = SecurityValidations::validateYield($yield);
$basis = SecurityValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
$daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
$daysBetweenIssueAndSettlement *= $daysPerYear;
$daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis));
if (!is_numeric($daysBetweenIssueAndMaturity)) {
// return date error
return $daysBetweenIssueAndMaturity;
}
$daysBetweenIssueAndMaturity *= $daysPerYear;
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
}
$daysBetweenSettlementAndMaturity *= $daysPerYear;
return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100))
/ (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield))
- (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100);
}
/**
* RECEIVED.
*
* Returns the amount received at maturity for a fully invested Security.
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue date when the security
* is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $investment The amount invested in the security
* @param mixed $discount The security's discount rate
* @param mixed $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string Result, or a string containing an error
*/
public static function received(
mixed $settlement,
mixed $maturity,
mixed $investment,
mixed $discount,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
) {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$investment = Functions::flattenSingleValue($investment);
$discount = Functions::flattenSingleValue($discount);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
$maturity = SecurityValidations::validateMaturityDate($maturity);
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
$investment = SecurityValidations::validateFloat($investment);
$discount = SecurityValidations::validateDiscount($discount);
$basis = SecurityValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
if ($investment <= 0) {
return ExcelError::NAN();
}
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis);
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return Functions::scalar($daysBetweenSettlementAndMaturity);
}
return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity));
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php 0000644 00000013415 15167673465 0023351 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Rates
{
/**
* DISC.
*
* Returns the discount rate for a security.
*
* Excel Function:
* DISC(settlement,maturity,price,redemption[,basis])
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue
* date when the security is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $price The security's price per $100 face value
* @param mixed $redemption The security's redemption value per $100 face value
* @param mixed $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*/
public static function discount(
mixed $settlement,
mixed $maturity,
mixed $price,
mixed $redemption,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
): float|string {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$price = Functions::flattenSingleValue($price);
$redemption = Functions::flattenSingleValue($redemption);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
$maturity = SecurityValidations::validateMaturityDate($maturity);
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
$price = SecurityValidations::validatePrice($price);
$redemption = SecurityValidations::validateRedemption($redemption);
$basis = SecurityValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
if ($price <= 0.0) {
return ExcelError::NAN();
}
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
}
return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity;
}
/**
* INTRATE.
*
* Returns the interest rate for a fully invested security.
*
* Excel Function:
* INTRATE(settlement,maturity,investment,redemption[,basis])
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue date when the security
* is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $investment the amount invested in the security
* @param mixed $redemption the amount to be received at maturity
* @param mixed $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*/
public static function interest(
mixed $settlement,
mixed $maturity,
mixed $investment,
mixed $redemption,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
): float|string {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$investment = Functions::flattenSingleValue($investment);
$redemption = Functions::flattenSingleValue($redemption);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
$maturity = SecurityValidations::validateMaturityDate($maturity);
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
$investment = SecurityValidations::validateFloat($investment);
$redemption = SecurityValidations::validateRedemption($redemption);
$basis = SecurityValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
if ($investment <= 0) {
return ExcelError::NAN();
}
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
}
return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php 0000644 00000001631 15167673465 0026275 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\FinancialValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class SecurityValidations extends FinancialValidations
{
public static function validateIssueDate(mixed $issue): float
{
return self::validateDate($issue);
}
public static function validateSecurityPeriod(mixed $settlement, mixed $maturity): void
{
if ($settlement >= $maturity) {
throw new Exception(ExcelError::NAN());
}
}
public static function validateRedemption(mixed $redemption): float
{
$redemption = self::validateFloat($redemption);
if ($redemption <= 0.0) {
throw new Exception(ExcelError::NAN());
}
return $redemption;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php 0000644 00000015343 15167673465 0025361 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\YearFrac;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class AccruedInterest
{
public const ACCRINT_CALCMODE_ISSUE_TO_SETTLEMENT = true;
public const ACCRINT_CALCMODE_FIRST_INTEREST_TO_SETTLEMENT = false;
/**
* ACCRINT.
*
* Returns the accrued interest for a security that pays periodic interest.
*
* Excel Function:
* ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis][,calc_method])
*
* @param mixed $issue the security's issue date
* @param mixed $firstInterest the security's first interest date
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue date
* when the security is traded to the buyer.
* @param mixed $rate The security's annual coupon rate
* @param mixed $parValue The security's par value.
* If you omit par, ACCRINT uses $1,000.
* @param mixed $frequency The number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param mixed $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
* @param mixed $calcMethod Unused by PhpSpreadsheet, and apparently by Excel (https://exceljet.net/functions/accrint-function)
*
* @return float|string Result, or a string containing an error
*/
public static function periodic(
mixed $issue,
mixed $firstInterest,
mixed $settlement,
mixed $rate,
mixed $parValue = 1000,
mixed $frequency = FinancialConstants::FREQUENCY_ANNUAL,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD,
mixed $calcMethod = self::ACCRINT_CALCMODE_ISSUE_TO_SETTLEMENT
) {
$issue = Functions::flattenSingleValue($issue);
$firstInterest = Functions::flattenSingleValue($firstInterest);
$settlement = Functions::flattenSingleValue($settlement);
$rate = Functions::flattenSingleValue($rate);
$parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue);
$frequency = ($frequency === null)
? FinancialConstants::FREQUENCY_ANNUAL
: Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$issue = SecurityValidations::validateIssueDate($issue);
$settlement = SecurityValidations::validateSettlementDate($settlement);
SecurityValidations::validateSecurityPeriod($issue, $settlement);
$rate = SecurityValidations::validateRate($rate);
$parValue = SecurityValidations::validateParValue($parValue);
SecurityValidations::validateFrequency($frequency);
$basis = SecurityValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
$daysBetweenFirstInterestAndSettlement = Functions::scalar(YearFrac::fraction($firstInterest, $settlement, $basis));
if (!is_numeric($daysBetweenFirstInterestAndSettlement)) {
// return date error
return $daysBetweenFirstInterestAndSettlement;
}
return $parValue * $rate * $daysBetweenIssueAndSettlement;
}
/**
* ACCRINTM.
*
* Returns the accrued interest for a security that pays interest at maturity.
*
* Excel Function:
* ACCRINTM(issue,settlement,rate[,par[,basis]])
*
* @param mixed $issue The security's issue date
* @param mixed $settlement The security's settlement (or maturity) date
* @param mixed $rate The security's annual coupon rate
* @param mixed $parValue The security's par value.
* If you omit parValue, ACCRINT uses $1,000.
* @param mixed $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string Result, or a string containing an error
*/
public static function atMaturity(
mixed $issue,
mixed $settlement,
mixed $rate,
mixed $parValue = 1000,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
) {
$issue = Functions::flattenSingleValue($issue);
$settlement = Functions::flattenSingleValue($settlement);
$rate = Functions::flattenSingleValue($rate);
$parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$issue = SecurityValidations::validateIssueDate($issue);
$settlement = SecurityValidations::validateSettlementDate($settlement);
SecurityValidations::validateSecurityPeriod($issue, $settlement);
$rate = SecurityValidations::validateRate($rate);
$parValue = SecurityValidations::validateParValue($parValue);
$basis = SecurityValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
return $parValue * $rate * $daysBetweenIssueAndSettlement;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php 0000644 00000016177 15167673465 0023534 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Yields
{
/**
* YIELDDISC.
*
* Returns the annual yield of a security that pays interest at maturity.
*
* @param mixed $settlement The security's settlement date.
* The security's settlement date is the date after the issue date when the security
* is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $price The security's price per $100 face value
* @param mixed $redemption The security's redemption value per $100 face value
* @param mixed $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string Result, or a string containing an error
*/
public static function yieldDiscounted(
mixed $settlement,
mixed $maturity,
mixed $price,
mixed $redemption,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
) {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$price = Functions::flattenSingleValue($price);
$redemption = Functions::flattenSingleValue($redemption);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
$maturity = SecurityValidations::validateMaturityDate($maturity);
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
$price = SecurityValidations::validatePrice($price);
$redemption = SecurityValidations::validateRedemption($redemption);
$basis = SecurityValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
}
$daysBetweenSettlementAndMaturity *= $daysPerYear;
return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
}
/**
* YIELDMAT.
*
* Returns the annual yield of a security that pays interest at maturity.
*
* @param mixed $settlement The security's settlement date.
* The security's settlement date is the date after the issue date when the security
* is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $issue The security's issue date
* @param mixed $rate The security's interest rate at date of issue
* @param mixed $price The security's price per $100 face value
* @param mixed $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string Result, or a string containing an error
*/
public static function yieldAtMaturity(
mixed $settlement,
mixed $maturity,
mixed $issue,
mixed $rate,
mixed $price,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
) {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$issue = Functions::flattenSingleValue($issue);
$rate = Functions::flattenSingleValue($rate);
$price = Functions::flattenSingleValue($price);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
$maturity = SecurityValidations::validateMaturityDate($maturity);
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
$issue = SecurityValidations::validateIssueDate($issue);
$rate = SecurityValidations::validateRate($rate);
$price = SecurityValidations::validatePrice($price);
$basis = SecurityValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
$daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
$daysBetweenIssueAndSettlement *= $daysPerYear;
$daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis));
if (!is_numeric($daysBetweenIssueAndMaturity)) {
// return date error
return $daysBetweenIssueAndMaturity;
}
$daysBetweenIssueAndMaturity *= $daysPerYear;
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
}
$daysBetweenSettlementAndMaturity *= $daysPerYear;
return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate)
- (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate)))
/ (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate)))
* ($daysPerYear / $daysBetweenSettlementAndMaturity);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php 0000644 00000013602 15167673465 0022573 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class TreasuryBill
{
/**
* TBILLEQ.
*
* Returns the bond-equivalent yield for a Treasury bill.
*
* @param mixed $settlement The Treasury bill's settlement date.
* The Treasury bill's settlement date is the date after the issue date
* when the Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date.
* The maturity date is the date when the Treasury bill expires.
* @param mixed $discount The Treasury bill's discount rate
*
* @return float|string Result, or a string containing an error
*/
public static function bondEquivalentYield(mixed $settlement, mixed $maturity, mixed $discount): string|float
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$discount = Functions::flattenSingleValue($discount);
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
$maturity = FinancialValidations::validateMaturityDate($maturity);
$discount = FinancialValidations::validateFloat($discount);
} catch (Exception $e) {
return $e->getMessage();
}
if ($discount <= 0) {
return ExcelError::NAN();
}
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
$daysPerYear = Helpers::daysPerYear(
Functions::scalar(DateTimeExcel\DateParts::year($maturity)),
FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
);
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) {
return ExcelError::NAN();
}
return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity);
}
/**
* TBILLPRICE.
*
* Returns the price per $100 face value for a Treasury bill.
*
* @param mixed $settlement The Treasury bill's settlement date.
* The Treasury bill's settlement date is the date after the issue date
* when the Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date.
* The maturity date is the date when the Treasury bill expires.
* @param mixed $discount The Treasury bill's discount rate
*
* @return float|string Result, or a string containing an error
*/
public static function price(mixed $settlement, mixed $maturity, mixed $discount): string|float
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$discount = Functions::flattenSingleValue($discount);
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
$maturity = FinancialValidations::validateMaturityDate($maturity);
$discount = FinancialValidations::validateFloat($discount);
} catch (Exception $e) {
return $e->getMessage();
}
if ($discount <= 0) {
return ExcelError::NAN();
}
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
$daysPerYear = Helpers::daysPerYear(
Functions::scalar(DateTimeExcel\DateParts::year($maturity)),
FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
);
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) {
return ExcelError::NAN();
}
$price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360));
if ($price < 0.0) {
return ExcelError::NAN();
}
return $price;
}
/**
* TBILLYIELD.
*
* Returns the yield for a Treasury bill.
*
* @param mixed $settlement The Treasury bill's settlement date.
* The Treasury bill's settlement date is the date after the issue date when
* the Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date.
* The maturity date is the date when the Treasury bill expires.
* @param float|string $price The Treasury bill's price per $100 face value
*/
public static function yield(mixed $settlement, mixed $maturity, $price): string|float
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$price = Functions::flattenSingleValue($price);
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
$maturity = FinancialValidations::validateMaturityDate($maturity);
$price = FinancialValidations::validatePrice($price);
} catch (Exception $e) {
return $e->getMessage();
}
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
$daysPerYear = Helpers::daysPerYear(
Functions::scalar(DateTimeExcel\DateParts::year($maturity)),
FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
);
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) {
return ExcelError::NAN();
}
return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php 0000644 00000004652 15167673465 0022570 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class InterestRate
{
/**
* EFFECT.
*
* Returns the effective interest rate given the nominal rate and the number of
* compounding payments per year.
*
* Excel Function:
* EFFECT(nominal_rate,npery)
*
* @param mixed $nominalRate Nominal interest rate as a float
* @param mixed $periodsPerYear Integer number of compounding payments per year
*/
public static function effective(mixed $nominalRate = 0, mixed $periodsPerYear = 0): string|float
{
$nominalRate = Functions::flattenSingleValue($nominalRate);
$periodsPerYear = Functions::flattenSingleValue($periodsPerYear);
try {
$nominalRate = FinancialValidations::validateFloat($nominalRate);
$periodsPerYear = FinancialValidations::validateInt($periodsPerYear);
} catch (Exception $e) {
return $e->getMessage();
}
if ($nominalRate <= 0 || $periodsPerYear < 1) {
return ExcelError::NAN();
}
return ((1 + $nominalRate / $periodsPerYear) ** $periodsPerYear) - 1;
}
/**
* NOMINAL.
*
* Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
*
* @param mixed $effectiveRate Effective interest rate as a float
* @param mixed $periodsPerYear Integer number of compounding payments per year
*
* @return float|string Result, or a string containing an error
*/
public static function nominal(mixed $effectiveRate = 0, mixed $periodsPerYear = 0): string|float
{
$effectiveRate = Functions::flattenSingleValue($effectiveRate);
$periodsPerYear = Functions::flattenSingleValue($periodsPerYear);
try {
$effectiveRate = FinancialValidations::validateFloat($effectiveRate);
$periodsPerYear = FinancialValidations::validateInt($periodsPerYear);
} catch (Exception $e) {
return $e->getMessage();
}
if ($effectiveRate <= 0 || $periodsPerYear < 1) {
return ExcelError::NAN();
}
// Calculate
return $periodsPerYear * (($effectiveRate + 1) ** (1 / $periodsPerYear) - 1);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Coupons.php 0000644 00000044052 15167673465 0021603 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date;
class Coupons
{
private const PERIOD_DATE_PREVIOUS = false;
private const PERIOD_DATE_NEXT = true;
/**
* COUPDAYBS.
*
* Returns the number of days from the beginning of the coupon period to the settlement date.
*
* Excel Function:
* COUPDAYBS(settlement,maturity,frequency[,basis])
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue
* date when the security is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $frequency The number of coupon payments per year (int).
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param mixed $basis The type of day count to use (int).
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*/
public static function COUPDAYBS(
mixed $settlement,
mixed $maturity,
mixed $frequency,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
): string|int|float {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
$maturity = FinancialValidations::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = FinancialValidations::validateFrequency($frequency);
$basis = FinancialValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
if (is_string($daysPerYear)) {
return ExcelError::VALUE();
}
$prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) {
return abs((float) DateTimeExcel\Days::between($prev, $settlement));
}
return (float) DateTimeExcel\YearFrac::fraction($prev, $settlement, $basis) * $daysPerYear;
}
/**
* COUPDAYS.
*
* Returns the number of days in the coupon period that contains the settlement date.
*
* Excel Function:
* COUPDAYS(settlement,maturity,frequency[,basis])
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue
* date when the security is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $frequency The number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param mixed $basis The type of day count to use (int).
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*/
public static function COUPDAYS(
mixed $settlement,
mixed $maturity,
mixed $frequency,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
): string|int|float {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
$maturity = FinancialValidations::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = FinancialValidations::validateFrequency($frequency);
$basis = FinancialValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
switch ($basis) {
case FinancialConstants::BASIS_DAYS_PER_YEAR_365:
// Actual/365
return 365 / $frequency;
case FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL:
// Actual/actual
if ($frequency == FinancialConstants::FREQUENCY_ANNUAL) {
$daysPerYear = (int) Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
return $daysPerYear / $frequency;
}
$prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
$next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
return $next - $prev;
default:
// US (NASD) 30/360, Actual/360 or European 30/360
return 360 / $frequency;
}
}
/**
* COUPDAYSNC.
*
* Returns the number of days from the settlement date to the next coupon date.
*
* Excel Function:
* COUPDAYSNC(settlement,maturity,frequency[,basis])
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue
* date when the security is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $frequency The number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param mixed $basis The type of day count to use (int) .
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*/
public static function COUPDAYSNC(
mixed $settlement,
mixed $maturity,
mixed $frequency,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
): string|float {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
$maturity = FinancialValidations::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = FinancialValidations::validateFrequency($frequency);
$basis = FinancialValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
/** @var int $daysPerYear */
$daysPerYear = Helpers::daysPerYear(Functions::Scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
$next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_NASD) {
$settlementDate = Date::excelToDateTimeObject($settlement);
$settlementEoM = Helpers::isLastDayOfMonth($settlementDate);
if ($settlementEoM) {
++$settlement;
}
}
return (float) DateTimeExcel\YearFrac::fraction($settlement, $next, $basis) * $daysPerYear;
}
/**
* COUPNCD.
*
* Returns the next coupon date after the settlement date.
*
* Excel Function:
* COUPNCD(settlement,maturity,frequency[,basis])
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue
* date when the security is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $frequency The number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param mixed $basis The type of day count to use (int).
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string Excel date/time serial value or error message
*/
public static function COUPNCD(
mixed $settlement,
mixed $maturity,
mixed $frequency,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
): string|float {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
$maturity = FinancialValidations::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = FinancialValidations::validateFrequency($frequency);
FinancialValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
}
/**
* COUPNUM.
*
* Returns the number of coupons payable between the settlement date and maturity date,
* rounded up to the nearest whole coupon.
*
* Excel Function:
* COUPNUM(settlement,maturity,frequency[,basis])
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue
* date when the security is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $frequency The number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param mixed $basis The type of day count to use (int).
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*/
public static function COUPNUM(
mixed $settlement,
mixed $maturity,
mixed $frequency,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
): string|int {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
$maturity = FinancialValidations::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = FinancialValidations::validateFrequency($frequency);
FinancialValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$yearsBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction(
$settlement,
$maturity,
FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
);
return (int) ceil((float) $yearsBetweenSettlementAndMaturity * $frequency);
}
/**
* COUPPCD.
*
* Returns the previous coupon date before the settlement date.
*
* Excel Function:
* COUPPCD(settlement,maturity,frequency[,basis])
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue
* date when the security is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $frequency The number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param mixed $basis The type of day count to use (int).
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string Excel date/time serial value or error message
*/
public static function COUPPCD(
mixed $settlement,
mixed $maturity,
mixed $frequency,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
): string|float {
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
$maturity = FinancialValidations::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = FinancialValidations::validateFrequency($frequency);
FinancialValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
}
private static function monthsDiff(DateTime $result, int $months, string $plusOrMinus, int $day, bool $lastDayFlag): void
{
$result->setDate((int) $result->format('Y'), (int) $result->format('m'), 1);
$result->modify("$plusOrMinus $months months");
$daysInMonth = (int) $result->format('t');
$result->setDate((int) $result->format('Y'), (int) $result->format('m'), $lastDayFlag ? $daysInMonth : min($day, $daysInMonth));
}
private static function couponFirstPeriodDate(float $settlement, float $maturity, int $frequency, bool $next): float
{
$months = 12 / $frequency;
$result = Date::excelToDateTimeObject($maturity);
$day = (int) $result->format('d');
$lastDayFlag = Helpers::isLastDayOfMonth($result);
while ($settlement < Date::PHPToExcel($result)) {
self::monthsDiff($result, $months, '-', $day, $lastDayFlag);
}
if ($next === true) {
self::monthsDiff($result, $months, '+', $day, $lastDayFlag);
}
return (float) Date::PHPToExcel($result);
}
private static function validateCouponPeriod(float $settlement, float $maturity): void
{
if ($settlement >= $maturity) {
throw new Exception(ExcelError::NAN());
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Amortization.php 0000644 00000020745 15167673465 0022640 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Amortization
{
private const ROUNDING_ADJUSTMENT = (PHP_VERSION_ID < 80400) ? 0 : 1e-14;
/**
* AMORDEGRC.
*
* Returns the depreciation for each accounting period.
* This function is provided for the French accounting system. If an asset is purchased in
* the middle of the accounting period, the prorated depreciation is taken into account.
* The function is similar to AMORLINC, except that a depreciation coefficient is applied in
* the calculation depending on the life of the assets.
* This function will return the depreciation until the last period of the life of the assets
* or until the cumulated value of depreciation is greater than the cost of the assets minus
* the salvage value.
*
* Excel Function:
* AMORDEGRC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
*
* @param mixed $cost The float cost of the asset
* @param mixed $purchased Date of the purchase of the asset
* @param mixed $firstPeriod Date of the end of the first period
* @param mixed $salvage The salvage value at the end of the life of the asset
* @param mixed $period the period (float)
* @param mixed $rate rate of depreciation (float)
* @param mixed $basis The type of day count to use (int).
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string (string containing the error type if there is an error)
*/
public static function AMORDEGRC(
mixed $cost,
mixed $purchased,
mixed $firstPeriod,
mixed $salvage,
mixed $period,
mixed $rate,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
): string|float {
$cost = Functions::flattenSingleValue($cost);
$purchased = Functions::flattenSingleValue($purchased);
$firstPeriod = Functions::flattenSingleValue($firstPeriod);
$salvage = Functions::flattenSingleValue($salvage);
$period = Functions::flattenSingleValue($period);
$rate = Functions::flattenSingleValue($rate);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$cost = FinancialValidations::validateFloat($cost);
$purchased = FinancialValidations::validateDate($purchased);
$firstPeriod = FinancialValidations::validateDate($firstPeriod);
$salvage = FinancialValidations::validateFloat($salvage);
$period = FinancialValidations::validateInt($period);
$rate = FinancialValidations::validateFloat($rate);
$basis = FinancialValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$yearFracx = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis);
if (is_string($yearFracx)) {
return $yearFracx;
}
/** @var float $yearFrac */
$yearFrac = $yearFracx;
$amortiseCoeff = self::getAmortizationCoefficient($rate);
$rate *= $amortiseCoeff;
$rate += self::ROUNDING_ADJUSTMENT;
$fNRate = round($yearFrac * $rate * $cost, 0);
$cost -= $fNRate;
$fRest = $cost - $salvage;
for ($n = 0; $n < $period; ++$n) {
$fNRate = round($rate * $cost, 0);
$fRest -= $fNRate;
if ($fRest < 0.0) {
return match ($period - $n) {
1 => round($cost * 0.5, 0),
default => 0.0,
};
}
$cost -= $fNRate;
}
return $fNRate;
}
/**
* AMORLINC.
*
* Returns the depreciation for each accounting period.
* This function is provided for the French accounting system. If an asset is purchased in
* the middle of the accounting period, the prorated depreciation is taken into account.
*
* Excel Function:
* AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
*
* @param mixed $cost The cost of the asset as a float
* @param mixed $purchased Date of the purchase of the asset
* @param mixed $firstPeriod Date of the end of the first period
* @param mixed $salvage The salvage value at the end of the life of the asset
* @param mixed $period The period as a float
* @param mixed $rate Rate of depreciation as float
* @param mixed $basis Integer indicating the type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string (string containing the error type if there is an error)
*/
public static function AMORLINC(
mixed $cost,
mixed $purchased,
mixed $firstPeriod,
mixed $salvage,
mixed $period,
mixed $rate,
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
): string|float {
$cost = Functions::flattenSingleValue($cost);
$purchased = Functions::flattenSingleValue($purchased);
$firstPeriod = Functions::flattenSingleValue($firstPeriod);
$salvage = Functions::flattenSingleValue($salvage);
$period = Functions::flattenSingleValue($period);
$rate = Functions::flattenSingleValue($rate);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
try {
$cost = FinancialValidations::validateFloat($cost);
$purchased = FinancialValidations::validateDate($purchased);
$firstPeriod = FinancialValidations::validateDate($firstPeriod);
$salvage = FinancialValidations::validateFloat($salvage);
$period = FinancialValidations::validateFloat($period);
$rate = FinancialValidations::validateFloat($rate);
$basis = FinancialValidations::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$fOneRate = $cost * $rate;
$fCostDelta = $cost - $salvage;
// Note, quirky variation for leap years on the YEARFRAC for this function
$purchasedYear = DateTimeExcel\DateParts::year($purchased);
$yearFracx = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis);
if (is_string($yearFracx)) {
return $yearFracx;
}
/** @var float $yearFrac */
$yearFrac = $yearFracx;
if (
$basis == FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
&& $yearFrac < 1
&& DateTimeExcel\Helpers::isLeapYear(Functions::scalar($purchasedYear))
) {
$yearFrac *= 365 / 366;
}
$f0Rate = $yearFrac * $rate * $cost;
$nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate);
if ($period == 0) {
return $f0Rate;
} elseif ($period <= $nNumOfFullPeriods) {
return $fOneRate;
} elseif ($period == ($nNumOfFullPeriods + 1)) {
return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate;
}
return 0.0;
}
private static function getAmortizationCoefficient(float $rate): float
{
// The depreciation coefficients are:
// Life of assets (1/rate) Depreciation coefficient
// Less than 3 years 1
// Between 3 and 4 years 1.5
// Between 5 and 6 years 2
// More than 6 years 2.5
$fUsePer = 1.0 / $rate;
if ($fUsePer < 3.0) {
return 1.0;
} elseif ($fUsePer < 4.0) {
return 1.5;
} elseif ($fUsePer <= 6.0) {
return 2.0;
}
return 2.5;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php 0000644 00000012356 15167673465 0021646 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Time
{
use ArrayEnabled;
/**
* TIME.
*
* The TIME function returns a value that represents a particular time.
*
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
*
* Excel Function:
* TIME(hour,minute,second)
*
* @param null|array|bool|float|int|string $hour A number from 0 (zero) to 32767 representing the hour.
* Any value greater than 23 will be divided by 24 and the remainder
* will be treated as the hour value. For example, TIME(27,0,0) =
* TIME(3,0,0) = .125 or 3:00 AM.
* @param null|array|bool|float|int|string $minute A number from 0 to 32767 representing the minute.
* Any value greater than 59 will be converted to hours and minutes.
* For example, TIME(0,750,0) = TIME(12,30,0) = .520833 or 12:30 PM.
* @param null|array|bool|float|int|string $second A number from 0 to 32767 representing the second.
* Any value greater than 59 will be converted to hours, minutes,
* and seconds. For example, TIME(0,0,2000) = TIME(0,33,22) = .023148
* or 12:33:20 AM
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function fromHMS(array|int|float|bool|null|string $hour, array|int|float|bool|null|string $minute, array|int|float|bool|null|string $second): array|string|float|int|DateTime
{
if (is_array($hour) || is_array($minute) || is_array($second)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $hour, $minute, $second);
}
try {
$hour = self::toIntWithNullBool($hour);
$minute = self::toIntWithNullBool($minute);
$second = self::toIntWithNullBool($second);
} catch (Exception $e) {
return $e->getMessage();
}
self::adjustSecond($second, $minute);
self::adjustMinute($minute, $hour);
if ($hour > 23) {
$hour = $hour % 24;
} elseif ($hour < 0) {
return ExcelError::NAN();
}
// Execute function
$retType = Functions::getReturnDateType();
if ($retType === Functions::RETURNDATE_EXCEL) {
$calendar = SharedDateHelper::getExcelCalendar();
$date = (int) ($calendar !== SharedDateHelper::CALENDAR_WINDOWS_1900);
return (float) SharedDateHelper::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second);
}
if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
return (int) SharedDateHelper::excelToTimestamp(SharedDateHelper::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600
}
// RETURNDATE_PHP_DATETIME_OBJECT
// Hour has already been normalized (0-23) above
$phpDateObject = new DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second);
return $phpDateObject;
}
private static function adjustSecond(int &$second, int &$minute): void
{
if ($second < 0) {
$minute += floor($second / 60);
$second = 60 - abs($second % 60);
if ($second == 60) {
$second = 0;
}
} elseif ($second >= 60) {
$minute += floor($second / 60);
$second = $second % 60;
}
}
private static function adjustMinute(int &$minute, int &$hour): void
{
if ($minute < 0) {
$hour += floor($minute / 60);
$minute = 60 - abs($minute % 60);
if ($minute == 60) {
$minute = 0;
}
} elseif ($minute >= 60) {
$hour += floor($minute / 60);
$minute = $minute % 60;
}
}
/**
* @param mixed $value expect int
*/
private static function toIntWithNullBool(mixed $value): int
{
$value = $value ?? 0;
if (is_bool($value)) {
$value = (int) $value;
}
if (!is_numeric($value)) {
throw new Exception(ExcelError::VALUE());
}
return (int) $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php 0000644 00000011140 15167673465 0022646 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class TimeParts
{
use ArrayEnabled;
/**
* HOUROFDAY.
*
* Returns the hour of a time value.
* The hour is given as an integer, ranging from 0 (12:00 A.M.) to 23 (11:00 P.M.).
*
* Excel Function:
* HOUR(timeValue)
*
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard time string
* Or can be an array of date/time values
*
* @return array|int|string Hour
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function hour(mixed $timeValue): array|string|int
{
if (is_array($timeValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
}
try {
Helpers::nullFalseTrueToNumber($timeValue);
if (!is_numeric($timeValue)) {
$timeValue = Helpers::getTimeValue($timeValue);
}
Helpers::validateNotNegative($timeValue);
} catch (Exception $e) {
return $e->getMessage();
}
// Execute function
$timeValue = fmod($timeValue, 1);
$timeValue = SharedDateHelper::excelToDateTimeObject($timeValue);
SharedDateHelper::roundMicroseconds($timeValue);
return (int) $timeValue->format('H');
}
/**
* MINUTE.
*
* Returns the minutes of a time value.
* The minute is given as an integer, ranging from 0 to 59.
*
* Excel Function:
* MINUTE(timeValue)
*
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard time string
* Or can be an array of date/time values
*
* @return array|int|string Minute
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function minute(mixed $timeValue): array|string|int
{
if (is_array($timeValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
}
try {
Helpers::nullFalseTrueToNumber($timeValue);
if (!is_numeric($timeValue)) {
$timeValue = Helpers::getTimeValue($timeValue);
}
Helpers::validateNotNegative($timeValue);
} catch (Exception $e) {
return $e->getMessage();
}
// Execute function
$timeValue = fmod($timeValue, 1);
$timeValue = SharedDateHelper::excelToDateTimeObject($timeValue);
SharedDateHelper::roundMicroseconds($timeValue);
return (int) $timeValue->format('i');
}
/**
* SECOND.
*
* Returns the seconds of a time value.
* The minute is given as an integer, ranging from 0 to 59.
*
* Excel Function:
* SECOND(timeValue)
*
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard time string
* Or can be an array of date/time values
*
* @return array|int|string Second
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function second(mixed $timeValue): array|string|int
{
if (is_array($timeValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
}
try {
Helpers::nullFalseTrueToNumber($timeValue);
if (!is_numeric($timeValue)) {
$timeValue = Helpers::getTimeValue($timeValue);
}
Helpers::validateNotNegative($timeValue);
} catch (Exception $e) {
return $e->getMessage();
}
// Execute function
$timeValue = fmod($timeValue, 1);
$timeValue = SharedDateHelper::excelToDateTimeObject($timeValue);
SharedDateHelper::roundMicroseconds($timeValue);
return (int) $timeValue->format('s');
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php 0000644 00000012532 15167673465 0022633 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class DateParts
{
use ArrayEnabled;
/**
* DAYOFMONTH.
*
* Returns the day of the month, for a specified date. The day is given as an integer
* ranging from 1 to 31.
*
* Excel Function:
* DAY(dateValue)
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return array|int|string Day of the month
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function day(mixed $dateValue): array|int|string
{
if (is_array($dateValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
}
$weirdResult = self::weirdCondition($dateValue);
if ($weirdResult >= 0) {
return $weirdResult;
}
try {
$dateValue = Helpers::getDateValue($dateValue);
} catch (Exception $e) {
return $e->getMessage();
}
// Execute function
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
SharedDateHelper::roundMicroseconds($PHPDateObject);
return (int) $PHPDateObject->format('j');
}
/**
* MONTHOFYEAR.
*
* Returns the month of a date represented by a serial number.
* The month is given as an integer, ranging from 1 (January) to 12 (December).
*
* Excel Function:
* MONTH(dateValue)
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return array|int|string Month of the year
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function month(mixed $dateValue): array|string|int
{
if (is_array($dateValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
}
try {
$dateValue = Helpers::getDateValue($dateValue);
} catch (Exception $e) {
return $e->getMessage();
}
if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) {
return 1;
}
// Execute function
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
SharedDateHelper::roundMicroseconds($PHPDateObject);
return (int) $PHPDateObject->format('n');
}
/**
* YEAR.
*
* Returns the year corresponding to a date.
* The year is returned as an integer in the range 1900-9999.
*
* Excel Function:
* YEAR(dateValue)
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return array|int|string Year
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function year(mixed $dateValue): array|string|int
{
if (is_array($dateValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
}
try {
$dateValue = Helpers::getDateValue($dateValue);
} catch (Exception $e) {
return $e->getMessage();
}
if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) {
return 1900;
}
// Execute function
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
SharedDateHelper::roundMicroseconds($PHPDateObject);
return (int) $PHPDateObject->format('Y');
}
/**
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
*/
private static function weirdCondition(mixed $dateValue): int
{
// Excel does not treat 0 consistently for DAY vs. (MONTH or YEAR)
if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900 && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) {
if (is_bool($dateValue)) {
return (int) $dateValue;
}
if ($dateValue === null) {
return 0;
}
if (is_numeric($dateValue) && $dateValue < 1 && $dateValue >= 0) {
return 0;
}
}
return -1;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php 0000644 00000002117 15167673465 0022716 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
class Constants
{
// Constants currently used by WeekNum; will eventually be used by WEEKDAY
const STARTWEEK_SUNDAY = 1;
const STARTWEEK_MONDAY = 2;
const STARTWEEK_MONDAY_ALT = 11;
const STARTWEEK_TUESDAY = 12;
const STARTWEEK_WEDNESDAY = 13;
const STARTWEEK_THURSDAY = 14;
const STARTWEEK_FRIDAY = 15;
const STARTWEEK_SATURDAY = 16;
const STARTWEEK_SUNDAY_ALT = 17;
const DOW_SUNDAY = 1;
const DOW_MONDAY = 2;
const DOW_TUESDAY = 3;
const DOW_WEDNESDAY = 4;
const DOW_THURSDAY = 5;
const DOW_FRIDAY = 6;
const DOW_SATURDAY = 7;
const STARTWEEK_MONDAY_ISO = 21;
const METHODARR = [
self::STARTWEEK_SUNDAY => self::DOW_SUNDAY,
self::DOW_MONDAY,
self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY,
self::DOW_TUESDAY,
self::DOW_WEDNESDAY,
self::DOW_THURSDAY,
self::DOW_FRIDAY,
self::DOW_SATURDAY,
self::DOW_SUNDAY,
self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO,
];
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php 0000644 00000004511 15167673465 0022364 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTime;
use DateTimeImmutable;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Current
{
/**
* DATENOW.
*
* Returns the current date.
* The NOW function is useful when you need to display the current date and time on a worksheet or
* calculate a value based on the current date and time, and have that value updated each time you
* open the worksheet.
*
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
* and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
*
* Excel Function:
* TODAY()
*
* @return DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
*/
public static function today(): DateTime|float|int|string
{
$dti = new DateTimeImmutable();
$dateArray = Helpers::dateParse($dti->format('c'));
return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray, true) : ExcelError::VALUE();
}
/**
* DATETIMENOW.
*
* Returns the current date and time.
* The NOW function is useful when you need to display the current date and time on a worksheet or
* calculate a value based on the current date and time, and have that value updated each time you
* open the worksheet.
*
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
* and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
*
* Excel Function:
* NOW()
*
* @return DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
*/
public static function now(): DateTime|float|int|string
{
$dti = new DateTimeImmutable();
$dateArray = Helpers::dateParse($dti->format('c'));
return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : ExcelError::VALUE();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php 0000644 00000007016 15167673465 0022640 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use Datetime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class TimeValue
{
use ArrayEnabled;
/**
* TIMEVALUE.
*
* Returns a value that represents a particular time.
* Use TIMEVALUE to convert a time represented by a text string to an Excel or PHP date/time stamp
* value.
*
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
*
* Excel Function:
* TIMEVALUE(timeValue)
*
* @param null|array|bool|float|int|string $timeValue A text string that represents a time in any one of the Microsoft
* Excel time formats; for example, "6:45 PM" and "18:45" text strings
* within quotation marks that represent time.
* Date information in time_text is ignored.
* Or can be an array of date/time values
*
* @return array|Datetime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function fromString(null|array|string|int|bool|float $timeValue): array|string|Datetime|int|float
{
if (is_array($timeValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
}
// try to parse as time iff there is at least one digit
if (is_string($timeValue) && preg_match('/\\d/', $timeValue) !== 1) {
return ExcelError::VALUE();
}
$timeValue = trim((string) $timeValue, '"');
$timeValue = str_replace(['/', '.'], '-', $timeValue);
$arraySplit = preg_split('/[\/:\-\s]/', $timeValue) ?: [];
if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) {
$arraySplit[0] = ($arraySplit[0] % 24);
$timeValue = implode(':', $arraySplit);
}
$PHPDateArray = Helpers::dateParse($timeValue);
$retValue = ExcelError::VALUE();
if (Helpers::dateParseSucceeded($PHPDateArray)) {
$hour = $PHPDateArray['hour'];
$minute = $PHPDateArray['minute'];
$second = $PHPDateArray['second'];
// OpenOffice-specific code removed - it works just like Excel
$excelDateValue = SharedDateHelper::formattedPHPToExcel(1900, 1, 1, $hour, $minute, $second) - 1;
$retType = Functions::getReturnDateType();
if ($retType === Functions::RETURNDATE_EXCEL) {
$retValue = (float) $excelDateValue;
} elseif ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
$retValue = (int) SharedDateHelper::excelToTimestamp($excelDateValue + 25569) - 3600;
} else {
$retValue = new Datetime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']);
}
}
return $retValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php 0000644 00000016463 15167673465 0022333 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class WorkDay
{
use ArrayEnabled;
/**
* WORKDAY.
*
* Returns the date that is the indicated number of working days before or after a date (the
* starting date). Working days exclude weekends and any dates identified as holidays.
* Use WORKDAY to exclude weekends or holidays when you calculate invoice due dates, expected
* delivery times, or the number of days of work performed.
*
* Excel Function:
* WORKDAY(startDate,endDays[,holidays[,holiday[,...]]])
*
* @param array|mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|int $endDays The number of nonweekend and nonholiday days before or after
* startDate. A positive value for days yields a future date; a
* negative value yields a past date.
* Or can be an array of int values
* @param null|mixed $dateArgs An array of dates (such as holidays) to exclude from the calculation
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function date(mixed $startDate, array|int|string $endDays, mixed ...$dateArgs): array|float|int|DateTime|string
{
if (is_array($startDate) || is_array($endDays)) {
return self::evaluateArrayArgumentsSubset(
[self::class, __FUNCTION__],
2,
$startDate,
$endDays,
...$dateArgs
);
}
// Retrieve the mandatory start date and days that are referenced in the function definition
try {
$startDate = Helpers::getDateValue($startDate);
$endDays = Helpers::validateNumericNull($endDays);
$holidayArray = array_map([Helpers::class, 'getDateValue'], Functions::flattenArray($dateArgs));
} catch (Exception $e) {
return $e->getMessage();
}
$startDate = (float) floor($startDate);
$endDays = (int) floor($endDays);
// If endDays is 0, we always return startDate
if ($endDays == 0) {
return $startDate;
}
if ($endDays < 0) {
return self::decrementing($startDate, $endDays, $holidayArray);
}
return self::incrementing($startDate, $endDays, $holidayArray);
}
/**
* Use incrementing logic to determine Workday.
*/
private static function incrementing(float $startDate, int $endDays, array $holidayArray): float|int|DateTime
{
// Adjust the start date if it falls over a weekend
$startDoW = self::getWeekDay($startDate, 3);
if ($startDoW >= 5) {
$startDate += 7 - $startDoW;
--$endDays;
}
// Add endDays
$endDate = (float) $startDate + ((int) ($endDays / 5) * 7);
$endDays = $endDays % 5;
while ($endDays > 0) {
++$endDate;
// Adjust the calculated end date if it falls over a weekend
$endDow = self::getWeekDay($endDate, 3);
if ($endDow >= 5) {
$endDate += 7 - $endDow;
}
--$endDays;
}
// Test any extra holiday parameters
if (!empty($holidayArray)) {
$endDate = self::incrementingArray($startDate, $endDate, $holidayArray);
}
return Helpers::returnIn3FormatsFloat($endDate);
}
private static function incrementingArray(float $startDate, float $endDate, array $holidayArray): float
{
$holidayCountedArray = $holidayDates = [];
foreach ($holidayArray as $holidayDate) {
if (self::getWeekDay($holidayDate, 3) < 5) {
$holidayDates[] = $holidayDate;
}
}
sort($holidayDates, SORT_NUMERIC);
foreach ($holidayDates as $holidayDate) {
if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
if (!in_array($holidayDate, $holidayCountedArray)) {
++$endDate;
$holidayCountedArray[] = $holidayDate;
}
}
// Adjust the calculated end date if it falls over a weekend
$endDoW = self::getWeekDay($endDate, 3);
if ($endDoW >= 5) {
$endDate += 7 - $endDoW;
}
}
return $endDate;
}
/**
* Use decrementing logic to determine Workday.
*/
private static function decrementing(float $startDate, int $endDays, array $holidayArray): float|int|DateTime
{
// Adjust the start date if it falls over a weekend
$startDoW = self::getWeekDay($startDate, 3);
if ($startDoW >= 5) {
$startDate += -$startDoW + 4;
++$endDays;
}
// Add endDays
$endDate = (float) $startDate + ((int) ($endDays / 5) * 7);
$endDays = $endDays % 5;
while ($endDays < 0) {
--$endDate;
// Adjust the calculated end date if it falls over a weekend
$endDow = self::getWeekDay($endDate, 3);
if ($endDow >= 5) {
$endDate += 4 - $endDow;
}
++$endDays;
}
// Test any extra holiday parameters
if (!empty($holidayArray)) {
$endDate = self::decrementingArray($startDate, $endDate, $holidayArray);
}
return Helpers::returnIn3FormatsFloat($endDate);
}
private static function decrementingArray(float $startDate, float $endDate, array $holidayArray): float
{
$holidayCountedArray = $holidayDates = [];
foreach ($holidayArray as $holidayDate) {
if (self::getWeekDay($holidayDate, 3) < 5) {
$holidayDates[] = $holidayDate;
}
}
rsort($holidayDates, SORT_NUMERIC);
foreach ($holidayDates as $holidayDate) {
if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) {
if (!in_array($holidayDate, $holidayCountedArray)) {
--$endDate;
$holidayCountedArray[] = $holidayDate;
}
}
// Adjust the calculated end date if it falls over a weekend
$endDoW = self::getWeekDay($endDate, 3);
/** int $endDoW */
if ($endDoW >= 5) {
$endDate += -$endDoW + 4;
}
}
return $endDate;
}
private static function getWeekDay(float $date, int $wd): int
{
$result = Functions::scalar(Week::day($date, $wd));
return is_int($result) ? $result : -1;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php 0000644 00000013637 15167673465 0023005 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateInterval;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Difference
{
use ArrayEnabled;
/**
* DATEDIF.
*
* @param mixed $startDate Excel date serial value, PHP date/time stamp, PHP DateTime object
* or a standard date string
* Or can be an array of date values
* @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object
* or a standard date string
* Or can be an array of date values
* @param array|string $unit Or can be an array of unit values
*
* @return array|int|string Interval between the dates
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function interval(mixed $startDate, mixed $endDate, array|string $unit = 'D')
{
if (is_array($startDate) || is_array($endDate) || is_array($unit)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $unit);
}
try {
$startDate = Helpers::getDateValue($startDate);
$endDate = Helpers::getDateValue($endDate);
$difference = self::initialDiff($startDate, $endDate);
$unit = strtoupper($unit);
} catch (Exception $e) {
return $e->getMessage();
}
// Execute function
$PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate);
$startDays = (int) $PHPStartDateObject->format('j');
//$startMonths = (int) $PHPStartDateObject->format('n');
$startYears = (int) $PHPStartDateObject->format('Y');
$PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate);
$endDays = (int) $PHPEndDateObject->format('j');
//$endMonths = (int) $PHPEndDateObject->format('n');
$endYears = (int) $PHPEndDateObject->format('Y');
$PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject);
$retVal = false;
$retVal = self::replaceRetValue($retVal, $unit, 'D') ?? self::datedifD($difference);
$retVal = self::replaceRetValue($retVal, $unit, 'M') ?? self::datedifM($PHPDiffDateObject);
$retVal = self::replaceRetValue($retVal, $unit, 'MD') ?? self::datedifMD($startDays, $endDays, $PHPEndDateObject, $PHPDiffDateObject);
$retVal = self::replaceRetValue($retVal, $unit, 'Y') ?? self::datedifY($PHPDiffDateObject);
$retVal = self::replaceRetValue($retVal, $unit, 'YD') ?? self::datedifYD($difference, $startYears, $endYears, $PHPStartDateObject, $PHPEndDateObject);
$retVal = self::replaceRetValue($retVal, $unit, 'YM') ?? self::datedifYM($PHPDiffDateObject);
return is_bool($retVal) ? ExcelError::VALUE() : $retVal;
}
private static function initialDiff(float $startDate, float $endDate): float
{
// Validate parameters
if ($startDate > $endDate) {
throw new Exception(ExcelError::NAN());
}
return $endDate - $startDate;
}
/**
* Decide whether it's time to set retVal.
*/
private static function replaceRetValue(bool|int $retVal, string $unit, string $compare): null|bool|int
{
if ($retVal !== false || $unit !== $compare) {
return $retVal;
}
return null;
}
private static function datedifD(float $difference): int
{
return (int) $difference;
}
private static function datedifM(DateInterval $PHPDiffDateObject): int
{
return 12 * (int) $PHPDiffDateObject->format('%y') + (int) $PHPDiffDateObject->format('%m');
}
private static function datedifMD(int $startDays, int $endDays, DateTime $PHPEndDateObject, DateInterval $PHPDiffDateObject): int
{
if ($endDays < $startDays) {
$retVal = $endDays;
$PHPEndDateObject->modify('-' . $endDays . ' days');
$adjustDays = (int) $PHPEndDateObject->format('j');
$retVal += ($adjustDays - $startDays);
} else {
$retVal = (int) $PHPDiffDateObject->format('%d');
}
return $retVal;
}
private static function datedifY(DateInterval $PHPDiffDateObject): int
{
return (int) $PHPDiffDateObject->format('%y');
}
private static function datedifYD(float $difference, int $startYears, int $endYears, DateTime $PHPStartDateObject, DateTime $PHPEndDateObject): int
{
$retVal = (int) $difference;
if ($endYears > $startYears) {
$isLeapStartYear = $PHPStartDateObject->format('L');
$wasLeapEndYear = $PHPEndDateObject->format('L');
// Adjust end year to be as close as possible as start year
while ($PHPEndDateObject >= $PHPStartDateObject) {
$PHPEndDateObject->modify('-1 year');
//$endYears = $PHPEndDateObject->format('Y');
}
$PHPEndDateObject->modify('+1 year');
// Get the result
$retVal = (int) $PHPEndDateObject->diff($PHPStartDateObject)->days;
// Adjust for leap years cases
$isLeapEndYear = $PHPEndDateObject->format('L');
$limit = new DateTime($PHPEndDateObject->format('Y-02-29'));
if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) {
--$retVal;
}
}
return (int) $retVal;
}
private static function datedifYM(DateInterval $PHPDiffDateObject): int
{
return (int) $PHPDiffDateObject->format('%m');
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php 0000644 00000012311 15167673465 0022070 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Days360
{
use ArrayEnabled;
/**
* DAYS360.
*
* Returns the number of days between two dates based on a 360-day year (twelve 30-day months),
* which is used in some accounting calculations. Use this function to help compute payments if
* your accounting system is based on twelve 30-day months.
*
* Excel Function:
* DAYS360(startDate,endDate[,method])
*
* @param array|mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|mixed $method US or European Method as a bool
* FALSE or omitted: U.S. (NASD) method. If the starting date is
* the last day of a month, it becomes equal to the 30th of the
* same month. If the ending date is the last day of a month and
* the starting date is earlier than the 30th of a month, the
* ending date becomes equal to the 1st of the next month;
* otherwise the ending date becomes equal to the 30th of the
* same month.
* TRUE: European method. Starting dates and ending dates that
* occur on the 31st of a month become equal to the 30th of the
* same month.
* Or can be an array of methods
*
* @return array|int|string Number of days between start date and end date
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function between(mixed $startDate = 0, mixed $endDate = 0, mixed $method = false): array|string|int
{
if (is_array($startDate) || is_array($endDate) || is_array($method)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $method);
}
try {
$startDate = Helpers::getDateValue($startDate);
$endDate = Helpers::getDateValue($endDate);
} catch (Exception $e) {
return $e->getMessage();
}
if (!is_bool($method)) {
return ExcelError::VALUE();
}
// Execute function
$PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate);
$startDay = $PHPStartDateObject->format('j');
$startMonth = $PHPStartDateObject->format('n');
$startYear = $PHPStartDateObject->format('Y');
$PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate);
$endDay = $PHPEndDateObject->format('j');
$endMonth = $PHPEndDateObject->format('n');
$endYear = $PHPEndDateObject->format('Y');
return self::dateDiff360((int) $startDay, (int) $startMonth, (int) $startYear, (int) $endDay, (int) $endMonth, (int) $endYear, !$method);
}
/**
* Return the number of days between two dates based on a 360 day calendar.
*/
private static function dateDiff360(int $startDay, int $startMonth, int $startYear, int $endDay, int $endMonth, int $endYear, bool $methodUS): int
{
$startDay = self::getStartDay($startDay, $startMonth, $startYear, $methodUS);
$endDay = self::getEndDay($endDay, $endMonth, $endYear, $startDay, $methodUS);
return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360;
}
private static function getStartDay(int $startDay, int $startMonth, int $startYear, bool $methodUS): int
{
if ($startDay == 31) {
--$startDay;
} elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !Helpers::isLeapYear($startYear))))) {
$startDay = 30;
}
return $startDay;
}
private static function getEndDay(int $endDay, int &$endMonth, int &$endYear, int $startDay, bool $methodUS): int
{
if ($endDay == 31) {
if ($methodUS && $startDay != 30) {
$endDay = 1;
if ($endMonth == 12) {
++$endYear;
$endMonth = 1;
} else {
++$endMonth;
}
} else {
$endDay = 30;
}
}
return $endDay;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php 0000644 00000013133 15167673465 0022436 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class YearFrac
{
use ArrayEnabled;
/**
* YEARFRAC.
*
* Calculates the fraction of the year represented by the number of whole days between two dates
* (the start_date and the end_date).
* Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or
* obligations to assign to a specific term.
*
* Excel Function:
* YEARFRAC(startDate,endDate[,method])
* See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html
* for description of algorithm used in Excel
*
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of values
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of methods
* @param array|int $method Method used for the calculation
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
* Or can be an array of methods
*
* @return array|float|int|string fraction of the year, or a string containing an error
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function fraction(mixed $startDate, mixed $endDate, array|int|string|null $method = 0): array|string|int|float
{
if (is_array($startDate) || is_array($endDate) || is_array($method)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $method);
}
try {
$method = (int) Helpers::validateNumericNull($method);
$sDate = Helpers::getDateValue($startDate);
$eDate = Helpers::getDateValue($endDate);
$sDate = self::excelBug($sDate, $startDate, $endDate, $method);
$eDate = self::excelBug($eDate, $endDate, $startDate, $method);
$startDate = min($sDate, $eDate);
$endDate = max($sDate, $eDate);
} catch (Exception $e) {
return $e->getMessage();
}
return match ($method) {
0 => Functions::scalar(Days360::between($startDate, $endDate)) / 360,
1 => self::method1($startDate, $endDate),
2 => Functions::scalar(Difference::interval($startDate, $endDate)) / 360,
3 => Functions::scalar(Difference::interval($startDate, $endDate)) / 365,
4 => Functions::scalar(Days360::between($startDate, $endDate, true)) / 360,
default => ExcelError::NAN(),
};
}
/**
* Excel 1900 calendar treats date argument of null as 1900-01-00. Really.
*/
private static function excelBug(float $sDate, mixed $startDate, mixed $endDate, int $method): float
{
if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE && SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) {
if ($endDate === null && $startDate !== null) {
if (DateParts::month($sDate) == 12 && DateParts::day($sDate) === 31 && $method === 0) {
$sDate += 2;
} else {
++$sDate;
}
}
}
return $sDate;
}
private static function method1(float $startDate, float $endDate): float
{
$days = Functions::scalar(Difference::interval($startDate, $endDate));
$startYear = (int) DateParts::year($startDate);
$endYear = (int) DateParts::year($endDate);
$years = $endYear - $startYear + 1;
$startMonth = (int) DateParts::month($startDate);
$startDay = (int) DateParts::day($startDate);
$endMonth = (int) DateParts::month($endDate);
$endDay = (int) DateParts::day($endDate);
$startMonthDay = 100 * $startMonth + $startDay;
$endMonthDay = 100 * $endMonth + $endDay;
if ($years == 1) {
$tmpCalcAnnualBasis = 365 + (int) Helpers::isLeapYear($endYear);
} elseif ($years == 2 && $startMonthDay >= $endMonthDay) {
if (Helpers::isLeapYear($startYear)) {
$tmpCalcAnnualBasis = 365 + (int) ($startMonthDay <= 229);
} elseif (Helpers::isLeapYear($endYear)) {
$tmpCalcAnnualBasis = 365 + (int) ($endMonthDay >= 229);
} else {
$tmpCalcAnnualBasis = 365;
}
} else {
$tmpCalcAnnualBasis = 0;
for ($year = $startYear; $year <= $endYear; ++$year) {
$tmpCalcAnnualBasis += 365 + (int) Helpers::isLeapYear($year);
}
$tmpCalcAnnualBasis /= $years;
}
return $days / $tmpCalcAnnualBasis;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php 0000644 00000004471 15167673465 0021647 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTimeInterface;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Days
{
use ArrayEnabled;
/**
* DAYS.
*
* Returns the number of days between two dates
*
* Excel Function:
* DAYS(endDate, startDate)
*
* @param array|DateTimeInterface|float|int|string $endDate Excel date serial value (float),
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|DateTimeInterface|float|int|string $startDate Excel date serial value (float),
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return array|int|string Number of days between start date and end date or an error
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function between(array|DateTimeInterface|float|int|string $endDate, array|DateTimeInterface|float|int|string $startDate): array|int|string
{
if (is_array($endDate) || is_array($startDate)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $endDate, $startDate);
}
try {
$startDate = Helpers::getDateValue($startDate);
$endDate = Helpers::getDateValue($endDate);
} catch (Exception $e) {
return $e->getMessage();
}
// Execute function
$PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate);
$PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate);
$days = ExcelError::VALUE();
$diff = $PHPStartDateObject->diff($PHPEndDateObject);
if ($diff !== false && !is_bool($diff->days)) {
$days = $diff->days;
if ($diff->invert) {
$days = -$days;
}
}
return $days;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php 0000644 00000024221 15167673465 0021635 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Week
{
use ArrayEnabled;
/**
* WEEKNUM.
*
* Returns the week of the year for a specified date.
* The WEEKNUM function considers the week containing January 1 to be the first week of the year.
* However, there is a European standard that defines the first week as the one with the majority
* of days (four or more) falling in the new year. This means that for years in which there are
* three days or less in the first week of January, the WEEKNUM function returns week numbers
* that are incorrect according to the European standard.
*
* Excel Function:
* WEEKNUM(dateValue[,style])
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|int $method Week begins on Sunday or Monday
* 1 or omitted Week begins on Sunday.
* 2 Week begins on Monday.
* 11 Week begins on Monday.
* 12 Week begins on Tuesday.
* 13 Week begins on Wednesday.
* 14 Week begins on Thursday.
* 15 Week begins on Friday.
* 16 Week begins on Saturday.
* 17 Week begins on Sunday.
* 21 ISO (Jan. 4 is week 1, begins on Monday).
* Or can be an array of methods
*
* @return array|int|string Week Number
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function number(mixed $dateValue, array|int|string|null $method = Constants::STARTWEEK_SUNDAY): array|int|string
{
if (is_array($dateValue) || is_array($method)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $method);
}
$origDateValueNull = empty($dateValue);
try {
$method = self::validateMethod($method);
if ($dateValue === null) { // boolean not allowed
$dateValue = (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904 || $method === Constants::DOW_SUNDAY) ? 0 : 1;
}
$dateValue = self::validateDateValue($dateValue);
if (!$dateValue && self::buggyWeekNum1900($method)) {
// This seems to be an additional Excel bug.
return 0;
}
} catch (Exception $e) {
return $e->getMessage();
}
// Execute function
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
if ($method == Constants::STARTWEEK_MONDAY_ISO) {
Helpers::silly1900($PHPDateObject);
return (int) $PHPDateObject->format('W');
}
if (self::buggyWeekNum1904($method, $origDateValueNull, $PHPDateObject)) {
return 0;
}
Helpers::silly1900($PHPDateObject, '+ 5 years'); // 1905 calendar matches
$dayOfYear = (int) $PHPDateObject->format('z');
$PHPDateObject->modify('-' . $dayOfYear . ' days');
$firstDayOfFirstWeek = (int) $PHPDateObject->format('w');
$daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7;
$daysInFirstWeek += 7 * !$daysInFirstWeek;
$endFirstWeek = $daysInFirstWeek - 1;
$weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7);
return (int) $weekOfYear;
}
/**
* ISOWEEKNUM.
*
* Returns the ISO 8601 week number of the year for a specified date.
*
* Excel Function:
* ISOWEEKNUM(dateValue)
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return array|int|string Week Number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isoWeekNumber(mixed $dateValue): array|int|string
{
if (is_array($dateValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
}
if (self::apparentBug($dateValue)) {
return 52;
}
try {
$dateValue = Helpers::getDateValue($dateValue);
} catch (Exception $e) {
return $e->getMessage();
}
// Execute function
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
Helpers::silly1900($PHPDateObject);
return (int) $PHPDateObject->format('W');
}
/**
* WEEKDAY.
*
* Returns the day of the week for a specified date. The day is given as an integer
* ranging from 0 to 7 (dependent on the requested style).
*
* Excel Function:
* WEEKDAY(dateValue[,style])
*
* @param null|array|bool|float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param mixed $style A number that determines the type of return value
* 1 or omitted Numbers 1 (Sunday) through 7 (Saturday).
* 2 Numbers 1 (Monday) through 7 (Sunday).
* 3 Numbers 0 (Monday) through 6 (Sunday).
* Or can be an array of styles
*
* @return array|int|string Day of the week value
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function day(null|array|float|int|string|bool $dateValue, mixed $style = 1): array|string|int
{
if (is_array($dateValue) || is_array($style)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $style);
}
try {
$dateValue = Helpers::getDateValue($dateValue);
$style = self::validateStyle($style);
} catch (Exception $e) {
return $e->getMessage();
}
// Execute function
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
Helpers::silly1900($PHPDateObject);
$DoW = (int) $PHPDateObject->format('w');
switch ($style) {
case 1:
++$DoW;
break;
case 2:
$DoW = self::dow0Becomes7($DoW);
break;
case 3:
$DoW = self::dow0Becomes7($DoW) - 1;
break;
}
return $DoW;
}
/**
* @param mixed $style expect int
*/
private static function validateStyle(mixed $style): int
{
if (!is_numeric($style)) {
throw new Exception(ExcelError::VALUE());
}
$style = (int) $style;
if (($style < 1) || ($style > 3)) {
throw new Exception(ExcelError::NAN());
}
return $style;
}
private static function dow0Becomes7(int $DoW): int
{
return ($DoW === 0) ? 7 : $DoW;
}
/**
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
*/
private static function apparentBug(mixed $dateValue): bool
{
if (SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) {
if (is_bool($dateValue)) {
return true;
}
if (is_numeric($dateValue) && !((int) $dateValue)) {
return true;
}
}
return false;
}
/**
* Validate dateValue parameter.
*/
private static function validateDateValue(mixed $dateValue): float
{
if (is_bool($dateValue)) {
throw new Exception(ExcelError::VALUE());
}
return Helpers::getDateValue($dateValue);
}
/**
* Validate method parameter.
*/
private static function validateMethod(mixed $method): int
{
if ($method === null) {
$method = Constants::STARTWEEK_SUNDAY;
}
if (!is_numeric($method)) {
throw new Exception(ExcelError::VALUE());
}
$method = (int) $method;
if (!array_key_exists($method, Constants::METHODARR)) {
throw new Exception(ExcelError::NAN());
}
$method = Constants::METHODARR[$method];
return $method;
}
private static function buggyWeekNum1900(int $method): bool
{
return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900;
}
private static function buggyWeekNum1904(int $method, bool $origNull, DateTime $dateObject): bool
{
// This appears to be another Excel bug.
return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904
&& !$origNull && $dateObject->format('Y-m-d') === '1904-01-01';
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php 0000644 00000016466 15167673465 0021633 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Date
{
use ArrayEnabled;
/**
* DATE.
*
* The DATE function returns a value that represents a particular date.
*
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
*
* Excel Function:
* DATE(year,month,day)
*
* PhpSpreadsheet is a lot more forgiving than MS Excel when passing non numeric values to this function.
* A Month name or abbreviation (English only at this point) such as 'January' or 'Jan' will still be accepted,
* as will a day value with a suffix (e.g. '21st' rather than simply 21); again only English language.
*
* @param array|float|int|string $year The value of the year argument can include one to four digits.
* Excel interprets the year argument according to the configured
* date system: 1900 or 1904.
* If year is between 0 (zero) and 1899 (inclusive), Excel adds that
* value to 1900 to calculate the year. For example, DATE(108,1,2)
* returns January 2, 2008 (1900+108).
* If year is between 1900 and 9999 (inclusive), Excel uses that
* value as the year. For example, DATE(2008,1,2) returns January 2,
* 2008.
* If year is less than 0 or is 10000 or greater, Excel returns the
* #NUM! error value.
* @param array|float|int|string $month A positive or negative integer representing the month of the year
* from 1 to 12 (January to December).
* If month is greater than 12, month adds that number of months to
* the first month in the year specified. For example, DATE(2008,14,2)
* returns the serial number representing February 2, 2009.
* If month is less than 1, month subtracts the magnitude of that
* number of months, plus 1, from the first month in the year
* specified. For example, DATE(2008,-3,2) returns the serial number
* representing September 2, 2007.
* @param array|float|int|string $day A positive or negative integer representing the day of the month
* from 1 to 31.
* If day is greater than the number of days in the month specified,
* day adds that number of days to the first day in the month. For
* example, DATE(2008,1,35) returns the serial number representing
* February 4, 2008.
* If day is less than 1, day subtracts the magnitude that number of
* days, plus one, from the first day of the month specified. For
* example, DATE(2008,1,-15) returns the serial number representing
* December 16, 2007.
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function fromYMD(array|float|int|string $year, array|float|int|string $month, array|float|int|string $day): float|int|DateTime|string|array
{
if (is_array($year) || is_array($month) || is_array($day)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $year, $month, $day);
}
$baseYear = SharedDateHelper::getExcelCalendar();
try {
$year = self::getYear($year, $baseYear);
$month = self::getMonth($month);
$day = self::getDay($day);
self::adjustYearMonth($year, $month, $baseYear);
} catch (Exception $e) {
return $e->getMessage();
}
// Execute function
$excelDateValue = SharedDateHelper::formattedPHPToExcel($year, $month, $day);
return Helpers::returnIn3FormatsFloat($excelDateValue);
}
/**
* Convert year from multiple formats to int.
*/
private static function getYear(mixed $year, int $baseYear): int
{
$year = ($year !== null) ? StringHelper::testStringAsNumeric((string) $year) : 0;
if (!is_numeric($year)) {
throw new Exception(ExcelError::VALUE());
}
$year = (int) $year;
if ($year < ($baseYear - 1900)) {
throw new Exception(ExcelError::NAN());
}
if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) {
throw new Exception(ExcelError::NAN());
}
if (($year < $baseYear) && ($year >= ($baseYear - 1900))) {
$year += 1900;
}
return (int) $year;
}
/**
* Convert month from multiple formats to int.
*/
private static function getMonth(mixed $month): int
{
if (($month !== null) && (!is_numeric($month))) {
$month = SharedDateHelper::monthStringToNumber($month);
}
$month = ($month !== null) ? StringHelper::testStringAsNumeric((string) $month) : 0;
if (!is_numeric($month)) {
throw new Exception(ExcelError::VALUE());
}
return (int) $month;
}
/**
* Convert day from multiple formats to int.
*/
private static function getDay(mixed $day): int
{
if (($day !== null) && (!is_numeric($day))) {
$day = SharedDateHelper::dayStringToNumber($day);
}
$day = ($day !== null) ? StringHelper::testStringAsNumeric((string) $day) : 0;
if (!is_numeric($day)) {
throw new Exception(ExcelError::VALUE());
}
return (int) $day;
}
private static function adjustYearMonth(int &$year, int &$month, int $baseYear): void
{
if ($month < 1) {
// Handle year/month adjustment if month < 1
--$month;
$year += ceil($month / 12) - 1;
$month = 13 - abs($month % 12);
} elseif ($month > 12) {
// Handle year/month adjustment if month > 12
$year += floor($month / 12);
$month = ($month % 12);
}
// Re-validate the year parameter after adjustments
if (($year < $baseYear) || ($year >= 10000)) {
throw new Exception(ExcelError::NAN());
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php 0000644 00000021611 15167673465 0022344 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Helpers
{
/**
* Identify if a year is a leap year or not.
*
* @param int|string $year The year to test
*
* @return bool TRUE if the year is a leap year, otherwise FALSE
*/
public static function isLeapYear(int|string $year): bool
{
return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0);
}
/**
* getDateValue.
*
* @return float Excel date/time serial value
*/
public static function getDateValue(mixed $dateValue, bool $allowBool = true): float
{
if (is_object($dateValue)) {
$retval = SharedDateHelper::PHPToExcel($dateValue);
if (is_bool($retval)) {
throw new Exception(ExcelError::VALUE());
}
return $retval;
}
self::nullFalseTrueToNumber($dateValue, $allowBool);
if (!is_numeric($dateValue)) {
$saveReturnDateType = Functions::getReturnDateType();
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
$dateValue = DateValue::fromString($dateValue);
Functions::setReturnDateType($saveReturnDateType);
if (!is_numeric($dateValue)) {
throw new Exception(ExcelError::VALUE());
}
}
if ($dateValue < 0 && Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
throw new Exception(ExcelError::NAN());
}
return (float) $dateValue;
}
/**
* getTimeValue.
*
* @return float|string Excel date/time serial value, or string if error
*/
public static function getTimeValue(string $timeValue): string|float
{
$saveReturnDateType = Functions::getReturnDateType();
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
/** @var float|string $timeValue */
$timeValue = TimeValue::fromString($timeValue);
Functions::setReturnDateType($saveReturnDateType);
return $timeValue;
}
/**
* Adjust date by given months.
*/
public static function adjustDateByMonths(mixed $dateValue = 0, float $adjustmentMonths = 0): DateTime
{
// Execute function
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
$oMonth = (int) $PHPDateObject->format('m');
$oYear = (int) $PHPDateObject->format('Y');
$adjustmentMonthsString = (string) $adjustmentMonths;
if ($adjustmentMonths > 0) {
$adjustmentMonthsString = '+' . $adjustmentMonths;
}
if ($adjustmentMonths != 0) {
$PHPDateObject->modify($adjustmentMonthsString . ' months');
}
$nMonth = (int) $PHPDateObject->format('m');
$nYear = (int) $PHPDateObject->format('Y');
$monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12);
if ($monthDiff != $adjustmentMonths) {
$adjustDays = (int) $PHPDateObject->format('d');
$adjustDaysString = '-' . $adjustDays . ' days';
$PHPDateObject->modify($adjustDaysString);
}
return $PHPDateObject;
}
/**
* Help reduce perceived complexity of some tests.
*/
public static function replaceIfEmpty(mixed &$value, mixed $altValue): void
{
$value = $value ?: $altValue;
}
/**
* Adjust year in ambiguous situations.
*/
public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void
{
if (!is_numeric($testVal1) || $testVal1 < 31) {
if (!is_numeric($testVal2) || $testVal2 < 12) {
if (is_numeric($testVal3) && $testVal3 < 12) {
$testVal3 += 2000;
}
}
}
}
/**
* Return result in one of three formats.
*/
public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false): DateTime|float|int
{
$retType = Functions::getReturnDateType();
if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
return new DateTime(
$dateArray['year']
. '-' . $dateArray['month']
. '-' . $dateArray['day']
. ' ' . $dateArray['hour']
. ':' . $dateArray['minute']
. ':' . $dateArray['second']
);
}
$excelDateValue
= SharedDateHelper::formattedPHPToExcel(
$dateArray['year'],
$dateArray['month'],
$dateArray['day'],
$dateArray['hour'],
$dateArray['minute'],
$dateArray['second']
);
if ($retType === Functions::RETURNDATE_EXCEL) {
return $noFrac ? floor($excelDateValue) : $excelDateValue;
}
// RETURNDATE_UNIX_TIMESTAMP)
return SharedDateHelper::excelToTimestamp($excelDateValue);
}
/**
* Return result in one of three formats.
*/
public static function returnIn3FormatsFloat(float $excelDateValue): float|int|DateTime
{
$retType = Functions::getReturnDateType();
if ($retType === Functions::RETURNDATE_EXCEL) {
return $excelDateValue;
}
if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
return SharedDateHelper::excelToTimestamp($excelDateValue);
}
// RETURNDATE_PHP_DATETIME_OBJECT
return SharedDateHelper::excelToDateTimeObject($excelDateValue);
}
/**
* Return result in one of three formats.
*/
public static function returnIn3FormatsObject(DateTime $PHPDateObject): DateTime|float|int
{
$retType = Functions::getReturnDateType();
if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
return $PHPDateObject;
}
if ($retType === Functions::RETURNDATE_EXCEL) {
return (float) SharedDateHelper::PHPToExcel($PHPDateObject);
}
// RETURNDATE_UNIX_TIMESTAMP
$stamp = SharedDateHelper::PHPToExcel($PHPDateObject);
$stamp = is_bool($stamp) ? ((int) $stamp) : $stamp;
return SharedDateHelper::excelToTimestamp($stamp);
}
private static function baseDate(): int
{
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
return 0;
}
if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904) {
return 0;
}
return 1;
}
/**
* Many functions accept null/false/true argument treated as 0/0/1.
*/
public static function nullFalseTrueToNumber(mixed &$number, bool $allowBool = true): void
{
$number = Functions::flattenSingleValue($number);
$nullVal = self::baseDate();
if ($number === null) {
$number = $nullVal;
} elseif ($allowBool && is_bool($number)) {
$number = $nullVal + (int) $number;
}
}
/**
* Many functions accept null argument treated as 0.
*/
public static function validateNumericNull(mixed $number): int|float
{
$number = Functions::flattenSingleValue($number);
if ($number === null) {
return 0;
}
if (is_int($number)) {
return $number;
}
if (is_numeric($number)) {
return (float) $number;
}
throw new Exception(ExcelError::VALUE());
}
/**
* Many functions accept null/false/true argument treated as 0/0/1.
*
* @phpstan-assert float $number
*/
public static function validateNotNegative(mixed $number): float
{
if (!is_numeric($number)) {
throw new Exception(ExcelError::VALUE());
}
if ($number >= 0) {
return (float) $number;
}
throw new Exception(ExcelError::NAN());
}
public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void
{
$isoDate = $PHPDateObject->format('c');
if ($isoDate < '1900-03-01') {
$PHPDateObject->modify($mod);
}
}
public static function dateParse(string $string): array
{
return self::forceArray(date_parse($string));
}
public static function dateParseSucceeded(array $dateArray): bool
{
return $dateArray['error_count'] === 0;
}
/**
* Despite documentation, date_parse probably never returns false.
* Just in case, this routine helps guarantee it.
*
* @param array|false $dateArray
*/
private static function forceArray(array|bool $dateArray): array
{
return is_array($dateArray) ? $dateArray : ['error_count' => 1];
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php 0000644 00000010345 15167673465 0023216 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class NetworkDays
{
use ArrayEnabled;
/**
* NETWORKDAYS.
*
* Returns the number of whole working days between start_date and end_date. Working days
* exclude weekends and any dates identified in holidays.
* Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days
* worked during a specific term.
*
* Excel Function:
* NETWORKDAYS(startDate,endDate[,holidays[,holiday[,...]]])
*
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param mixed $dateArgs An array of dates (such as holidays) to exclude from the calculation
*
* @return array|int|string Interval between the dates
* If an array of values is passed for the $startDate or $endDate arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function count(mixed $startDate, mixed $endDate, mixed ...$dateArgs): array|string|int
{
if (is_array($startDate) || is_array($endDate)) {
return self::evaluateArrayArgumentsSubset(
[self::class, __FUNCTION__],
2,
$startDate,
$endDate,
...$dateArgs
);
}
try {
// Retrieve the mandatory start and end date that are referenced in the function definition
$sDate = Helpers::getDateValue($startDate);
$eDate = Helpers::getDateValue($endDate);
$startDate = min($sDate, $eDate);
$endDate = max($sDate, $eDate);
// Get the optional days
$dateArgs = Functions::flattenArray($dateArgs);
// Test any extra holiday parameters
$holidayArray = [];
foreach ($dateArgs as $holidayDate) {
$holidayArray[] = Helpers::getDateValue($holidayDate);
}
} catch (Exception $e) {
return $e->getMessage();
}
// Execute function
$startDow = self::calcStartDow($startDate);
$endDow = self::calcEndDow($endDate);
$wholeWeekDays = (int) floor(($endDate - $startDate) / 7) * 5;
$partWeekDays = self::calcPartWeekDays($startDow, $endDow);
// Test any extra holiday parameters
$holidayCountedArray = [];
foreach ($holidayArray as $holidayDate) {
if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
if ((Week::day($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) {
--$partWeekDays;
$holidayCountedArray[] = $holidayDate;
}
}
}
return self::applySign($wholeWeekDays + $partWeekDays, $sDate, $eDate);
}
private static function calcStartDow(float $startDate): int
{
$startDow = 6 - (int) Week::day($startDate, 2);
if ($startDow < 0) {
$startDow = 5;
}
return $startDow;
}
private static function calcEndDow(float $endDate): int
{
$endDow = (int) Week::day($endDate, 2);
if ($endDow >= 6) {
$endDow = 0;
}
return $endDow;
}
private static function calcPartWeekDays(int $startDow, int $endDow): int
{
$partWeekDays = $endDow + $startDow;
if ($partWeekDays > 5) {
$partWeekDays -= 5;
}
return $partWeekDays;
}
private static function applySign(int $result, float $sDate, float $eDate): int
{
return ($sDate > $eDate) ? -$result : $result;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php 0000644 00000015532 15167673465 0022621 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTime;
use DateTimeImmutable;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class DateValue
{
use ArrayEnabled;
/**
* DATEVALUE.
*
* Returns a value that represents a particular date.
* Use DATEVALUE to convert a date represented by a text string to an Excel or PHP date/time stamp
* value.
*
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
*
* Excel Function:
* DATEVALUE(dateValue)
*
* @param null|array|bool|float|int|string $dateValue Text that represents a date in a Microsoft Excel date format.
* For example, "1/30/2008" or "30-Jan-2008" are text strings within
* quotation marks that represent dates. Using the default date
* system in Excel for Windows, date_text must represent a date from
* January 1, 1900, to December 31, 9999. Using the default date
* system in Excel for the Macintosh, date_text must represent a date
* from January 1, 1904, to December 31, 9999. DATEVALUE returns the
* #VALUE! error value if date_text is out of this range.
* Or can be an array of date values
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function fromString(null|array|string|int|bool|float $dateValue): array|string|float|int|DateTime
{
if (is_array($dateValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
}
// try to parse as date iff there is at least one digit
if (is_string($dateValue) && preg_match('/\\d/', $dateValue) !== 1) {
return ExcelError::VALUE();
}
$dti = new DateTimeImmutable();
$baseYear = SharedDateHelper::getExcelCalendar();
$dateValue = trim((string) $dateValue, '"');
// Strip any ordinals because they're allowed in Excel (English only)
$dateValue = (string) preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue);
// Convert separators (/ . or space) to hyphens (should also handle dot used for ordinals in some countries, e.g. Denmark, Germany)
$dateValue = str_replace(['/', '.', '-', ' '], ' ', $dateValue);
$yearFound = false;
$t1 = explode(' ', $dateValue);
$t = '';
foreach ($t1 as &$t) {
if ((is_numeric($t)) && ($t > 31)) {
if ($yearFound) {
return ExcelError::VALUE();
}
if ($t < 100) {
$t += 1900;
}
$yearFound = true;
}
}
if (count($t1) === 1) {
// We've been fed a time value without any date
return ((!str_contains((string) $t, ':'))) ? ExcelError::Value() : 0.0;
}
unset($t);
$dateValue = self::t1ToString($t1, $dti, $yearFound);
$PHPDateArray = self::setUpArray($dateValue, $dti);
return self::finalResults($PHPDateArray, $dti, $baseYear);
}
private static function t1ToString(array $t1, DateTimeImmutable $dti, bool $yearFound): string
{
if (count($t1) == 2) {
// We only have two parts of the date: either day/month or month/year
if ($yearFound) {
array_unshift($t1, 1);
} else {
if (is_numeric($t1[1]) && $t1[1] > 29) {
$t1[1] += 1900;
array_unshift($t1, 1);
} else {
$t1[] = $dti->format('Y');
}
}
}
$dateValue = implode(' ', $t1);
return $dateValue;
}
/**
* Parse date.
*/
private static function setUpArray(string $dateValue, DateTimeImmutable $dti): array
{
$PHPDateArray = Helpers::dateParse($dateValue);
if (!Helpers::dateParseSucceeded($PHPDateArray)) {
// If original count was 1, we've already returned.
// If it was 2, we added another.
// Therefore, neither of the first 2 stroks below can fail.
$testVal1 = strtok($dateValue, '- ');
$testVal2 = strtok('- ');
$testVal3 = strtok('- ') ?: $dti->format('Y');
Helpers::adjustYear((string) $testVal1, (string) $testVal2, $testVal3);
$PHPDateArray = Helpers::dateParse($testVal1 . '-' . $testVal2 . '-' . $testVal3);
if (!Helpers::dateParseSucceeded($PHPDateArray)) {
$PHPDateArray = Helpers::dateParse($testVal2 . '-' . $testVal1 . '-' . $testVal3);
}
}
return $PHPDateArray;
}
/**
* Final results.
*
* @return DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
*/
private static function finalResults(array $PHPDateArray, DateTimeImmutable $dti, int $baseYear): string|float|int|DateTime
{
$retValue = ExcelError::Value();
if (Helpers::dateParseSucceeded($PHPDateArray)) {
// Execute function
Helpers::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y'));
if ($PHPDateArray['year'] < $baseYear) {
return ExcelError::VALUE();
}
Helpers::replaceIfEmpty($PHPDateArray['month'], $dti->format('m'));
Helpers::replaceIfEmpty($PHPDateArray['day'], $dti->format('d'));
$PHPDateArray['hour'] = 0;
$PHPDateArray['minute'] = 0;
$PHPDateArray['second'] = 0;
$month = (int) $PHPDateArray['month'];
$day = (int) $PHPDateArray['day'];
$year = (int) $PHPDateArray['year'];
if (!checkdate($month, $day, $year)) {
return ($year === 1900 && $month === 2 && $day === 29) ? Helpers::returnIn3FormatsFloat(60.0) : ExcelError::VALUE();
}
$retValue = Helpers::returnIn3FormatsArray($PHPDateArray, true);
}
return $retValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php 0000644 00000011257 15167673465 0022034 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Month
{
use ArrayEnabled;
/**
* EDATE.
*
* Returns the serial number that represents the date that is the indicated number of months
* before or after a specified date (the start_date).
* Use EDATE to calculate maturity dates or due dates that fall on the same day of the month
* as the date of issue.
*
* Excel Function:
* EDATE(dateValue,adjustmentMonths)
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|int $adjustmentMonths The number of months before or after start_date.
* A positive value for months yields a future date;
* a negative value yields a past date.
* Or can be an array of adjustment values
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function adjust(mixed $dateValue, array|string|bool|float|int $adjustmentMonths): DateTime|float|int|string|array
{
if (is_array($dateValue) || is_array($adjustmentMonths)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $adjustmentMonths);
}
try {
$dateValue = Helpers::getDateValue($dateValue, false);
$adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths);
} catch (Exception $e) {
return $e->getMessage();
}
$dateValue = floor($dateValue);
$adjustmentMonths = floor($adjustmentMonths);
// Execute function
$PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths);
return Helpers::returnIn3FormatsObject($PHPDateObject);
}
/**
* EOMONTH.
*
* Returns the date value for the last day of the month that is the indicated number of months
* before or after start_date.
* Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month.
*
* Excel Function:
* EOMONTH(dateValue,adjustmentMonths)
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|int $adjustmentMonths The number of months before or after start_date.
* A positive value for months yields a future date;
* a negative value yields a past date.
* Or can be an array of adjustment values
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function lastDay(mixed $dateValue, array|float|int|bool|string $adjustmentMonths): array|string|DateTime|float|int
{
if (is_array($dateValue) || is_array($adjustmentMonths)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $adjustmentMonths);
}
try {
$dateValue = Helpers::getDateValue($dateValue, false);
$adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths);
} catch (Exception $e) {
return $e->getMessage();
}
$dateValue = floor($dateValue);
$adjustmentMonths = floor($adjustmentMonths);
// Execute function
$PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths + 1);
$adjustDays = (int) $PHPDateObject->format('d');
$adjustDaysString = '-' . $adjustDays . ' days';
$PHPDateObject->modify($adjustDaysString);
return Helpers::returnIn3FormatsObject($PHPDateObject);
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php 0000644 00000745174 15167673465 0020544 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\BranchPruner;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\CyclicReferenceStack;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\Logger;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\Operands;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Token\Stack;
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\DefinedName;
use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\ReferenceHelper;
use PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use ReflectionClassConstant;
use ReflectionMethod;
use ReflectionParameter;
use Throwable;
class Calculation
{
/** Constants */
/** Regular Expressions */
// Numeric operand
const CALCULATION_REGEXP_NUMBER = '[-+]?\d*\.?\d+(e[-+]?\d+)?';
// String operand
const CALCULATION_REGEXP_STRING = '"(?:[^"]|"")*"';
// Opening bracket
const CALCULATION_REGEXP_OPENBRACE = '\(';
// Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it)
const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?(?:_xlws\.)?([\p{L}][\p{L}\p{N}\.]*)[\s]*\(';
// Strip xlfn and xlws prefixes from function name
const CALCULATION_REGEXP_STRIP_XLFN_XLWS = '/(_xlfn[.])?(_xlws[.])?(?=[\p{L}][\p{L}\p{N}\.]*[\s]*[(])/';
// Cell reference (cell or range of cells, with or without a sheet reference)
const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])';
// Cell reference (with or without a sheet reference) ensuring absolute/relative
const CALCULATION_REGEXP_CELLREF_RELATIVE = '((([^\s\(,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?(\$?\b[a-z]{1,3})(\$?\d{1,7})(?![\w.])';
const CALCULATION_REGEXP_COLUMN_RANGE = '(((([^\s\(,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\".(?:[^\"]|\"[^!])?\"))!)?(\$?[a-z]{1,3})):(?![.*])';
const CALCULATION_REGEXP_ROW_RANGE = '(((([^\s\(,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?(\$?[1-9][0-9]{0,6})):(?![.*])';
// Cell reference (with or without a sheet reference) ensuring absolute/relative
// Cell ranges ensuring absolute/relative
const CALCULATION_REGEXP_COLUMNRANGE_RELATIVE = '(\$?[a-z]{1,3}):(\$?[a-z]{1,3})';
const CALCULATION_REGEXP_ROWRANGE_RELATIVE = '(\$?\d{1,7}):(\$?\d{1,7})';
// Defined Names: Named Range of cells, or Named Formulae
const CALCULATION_REGEXP_DEFINEDNAME = '((([^\s,!&%^\/\*\+<>=-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?([_\p{L}][_\p{L}\p{N}\.]*)';
// Structured Reference (Fully Qualified and Unqualified)
const CALCULATION_REGEXP_STRUCTURED_REFERENCE = '([\p{L}_\\\\][\p{L}\p{N}\._]+)?(\[(?:[^\d\]+-])?)';
// Error
const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?';
/** constants */
const RETURN_ARRAY_AS_ERROR = 'error';
const RETURN_ARRAY_AS_VALUE = 'value';
const RETURN_ARRAY_AS_ARRAY = 'array';
const FORMULA_OPEN_FUNCTION_BRACE = '(';
const FORMULA_CLOSE_FUNCTION_BRACE = ')';
const FORMULA_OPEN_MATRIX_BRACE = '{';
const FORMULA_CLOSE_MATRIX_BRACE = '}';
const FORMULA_STRING_QUOTE = '"';
private static string $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE;
/**
* Instance of this class.
*
* @var ?Calculation
*/
private static ?Calculation $instance = null;
/**
* Instance of the spreadsheet this Calculation Engine is using.
*/
private ?Spreadsheet $spreadsheet;
/**
* Calculation cache.
*/
private array $calculationCache = [];
/**
* Calculation cache enabled.
*/
private bool $calculationCacheEnabled = true;
private BranchPruner $branchPruner;
private bool $branchPruningEnabled = true;
/**
* List of operators that can be used within formulae
* The true/false value indicates whether it is a binary operator or a unary operator.
*/
private const CALCULATION_OPERATORS = [
'+' => true, '-' => true, '*' => true, '/' => true,
'^' => true, '&' => true, '%' => false, '~' => false,
'>' => true, '<' => true, '=' => true, '>=' => true,
'<=' => true, '<>' => true, '∩' => true, '∪' => true,
':' => true,
];
/**
* List of binary operators (those that expect two operands).
*/
private const BINARY_OPERATORS = [
'+' => true, '-' => true, '*' => true, '/' => true,
'^' => true, '&' => true, '>' => true, '<' => true,
'=' => true, '>=' => true, '<=' => true, '<>' => true,
'∩' => true, '∪' => true, ':' => true,
];
/**
* The debug log generated by the calculation engine.
*/
private Logger $debugLog;
private bool $suppressFormulaErrorsNew = false;
/**
* Error message for any error that was raised/thrown by the calculation engine.
*/
public ?string $formulaError = null;
/**
* Reference Helper.
*/
private static ReferenceHelper $referenceHelper;
/**
* An array of the nested cell references accessed by the calculation engine, used for the debug log.
*/
private CyclicReferenceStack $cyclicReferenceStack;
private array $cellStack = [];
/**
* Current iteration counter for cyclic formulae
* If the value is 0 (or less) then cyclic formulae will throw an exception,
* otherwise they will iterate to the limit defined here before returning a result.
*/
private int $cyclicFormulaCounter = 1;
private string $cyclicFormulaCell = '';
/**
* Number of iterations for cyclic formulae.
*/
public int $cyclicFormulaCount = 1;
/**
* The current locale setting.
*/
private static string $localeLanguage = 'en_us'; // US English (default locale)
/**
* List of available locale settings
* Note that this is read for the locale subdirectory only when requested.
*
* @var string[]
*/
private static array $validLocaleLanguages = [
'en', // English (default language)
];
/**
* Locale-specific argument separator for function arguments.
*/
private static string $localeArgumentSeparator = ',';
private static array $localeFunctions = [];
/**
* Locale-specific translations for Excel constants (True, False and Null).
*
* @var array<string, string>
*/
private static array $localeBoolean = [
'TRUE' => 'TRUE',
'FALSE' => 'FALSE',
'NULL' => 'NULL',
];
public static function getLocaleBoolean(string $index): string
{
return self::$localeBoolean[$index];
}
/**
* Excel constant string translations to their PHP equivalents
* Constant conversion from text name/value to actual (datatyped) value.
*
* @var array<string, null|bool>
*/
private static array $excelConstants = [
'TRUE' => true,
'FALSE' => false,
'NULL' => null,
];
public static function keyInExcelConstants(string $key): bool
{
return array_key_exists($key, self::$excelConstants);
}
public static function getExcelConstants(string $key): bool|null
{
return self::$excelConstants[$key];
}
/**
* Array of functions usable on Spreadsheet.
* In theory, this could be const rather than static;
* however, Phpstan breaks trying to analyze it when attempted.
*/
private static array $phpSpreadsheetFunctions = [
'ABS' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Absolute::class, 'evaluate'],
'argumentCount' => '1',
],
'ACCRINT' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities\AccruedInterest::class, 'periodic'],
'argumentCount' => '4-8',
],
'ACCRINTM' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities\AccruedInterest::class, 'atMaturity'],
'argumentCount' => '3-5',
],
'ACOS' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Cosine::class, 'acos'],
'argumentCount' => '1',
],
'ACOSH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Cosine::class, 'acosh'],
'argumentCount' => '1',
],
'ACOT' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Cotangent::class, 'acot'],
'argumentCount' => '1',
],
'ACOTH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Cotangent::class, 'acoth'],
'argumentCount' => '1',
],
'ADDRESS' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Address::class, 'cell'],
'argumentCount' => '2-5',
],
'AGGREGATE' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3+',
],
'AMORDEGRC' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Amortization::class, 'AMORDEGRC'],
'argumentCount' => '6,7',
],
'AMORLINC' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Amortization::class, 'AMORLINC'],
'argumentCount' => '6,7',
],
'ANCHORARRAY' => [
'category' => Category::CATEGORY_UNCATEGORISED,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '*',
],
'AND' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical\Operations::class, 'logicalAnd'],
'argumentCount' => '1+',
],
'ARABIC' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Arabic::class, 'evaluate'],
'argumentCount' => '1',
],
'AREAS' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1',
],
'ARRAYTOTEXT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Text::class, 'fromArray'],
'argumentCount' => '1,2',
],
'ASC' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1',
],
'ASIN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Sine::class, 'asin'],
'argumentCount' => '1',
],
'ASINH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Sine::class, 'asinh'],
'argumentCount' => '1',
],
'ATAN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Tangent::class, 'atan'],
'argumentCount' => '1',
],
'ATAN2' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Tangent::class, 'atan2'],
'argumentCount' => '2',
],
'ATANH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Tangent::class, 'atanh'],
'argumentCount' => '1',
],
'AVEDEV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Averages::class, 'averageDeviations'],
'argumentCount' => '1+',
],
'AVERAGE' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Averages::class, 'average'],
'argumentCount' => '1+',
],
'AVERAGEA' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Averages::class, 'averageA'],
'argumentCount' => '1+',
],
'AVERAGEIF' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Conditional::class, 'AVERAGEIF'],
'argumentCount' => '2,3',
],
'AVERAGEIFS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Conditional::class, 'AVERAGEIFS'],
'argumentCount' => '3+',
],
'BAHTTEXT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1',
],
'BASE' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Base::class, 'evaluate'],
'argumentCount' => '2,3',
],
'BESSELI' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\BesselI::class, 'BESSELI'],
'argumentCount' => '2',
],
'BESSELJ' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\BesselJ::class, 'BESSELJ'],
'argumentCount' => '2',
],
'BESSELK' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\BesselK::class, 'BESSELK'],
'argumentCount' => '2',
],
'BESSELY' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\BesselY::class, 'BESSELY'],
'argumentCount' => '2',
],
'BETADIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Beta::class, 'distribution'],
'argumentCount' => '3-5',
],
'BETA.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '4-6',
],
'BETAINV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'],
'argumentCount' => '3-5',
],
'BETA.INV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'],
'argumentCount' => '3-5',
],
'BIN2DEC' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertBinary::class, 'toDecimal'],
'argumentCount' => '1',
],
'BIN2HEX' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertBinary::class, 'toHex'],
'argumentCount' => '1,2',
],
'BIN2OCT' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertBinary::class, 'toOctal'],
'argumentCount' => '1,2',
],
'BINOMDIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'],
'argumentCount' => '4',
],
'BINOM.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'],
'argumentCount' => '4',
],
'BINOM.DIST.RANGE' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Binomial::class, 'range'],
'argumentCount' => '3,4',
],
'BINOM.INV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'],
'argumentCount' => '3',
],
'BITAND' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\BitWise::class, 'BITAND'],
'argumentCount' => '2',
],
'BITOR' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\BitWise::class, 'BITOR'],
'argumentCount' => '2',
],
'BITXOR' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\BitWise::class, 'BITXOR'],
'argumentCount' => '2',
],
'BITLSHIFT' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\BitWise::class, 'BITLSHIFT'],
'argumentCount' => '2',
],
'BITRSHIFT' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\BitWise::class, 'BITRSHIFT'],
'argumentCount' => '2',
],
'BYCOL' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '*',
],
'BYROW' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '*',
],
'CEILING' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Ceiling::class, 'ceiling'],
'argumentCount' => '1-2', // 2 for Excel, 1-2 for Ods/Gnumeric
],
'CEILING.MATH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Ceiling::class, 'math'],
'argumentCount' => '1-3',
],
'CEILING.PRECISE' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Ceiling::class, 'precise'],
'argumentCount' => '1,2',
],
'CELL' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1,2',
],
'CHAR' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\CharacterConvert::class, 'character'],
'argumentCount' => '1',
],
'CHIDIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'],
'argumentCount' => '2',
],
'CHISQ.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionLeftTail'],
'argumentCount' => '3',
],
'CHISQ.DIST.RT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'],
'argumentCount' => '2',
],
'CHIINV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'],
'argumentCount' => '2',
],
'CHISQ.INV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseLeftTail'],
'argumentCount' => '2',
],
'CHISQ.INV.RT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'],
'argumentCount' => '2',
],
'CHITEST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'],
'argumentCount' => '2',
],
'CHISQ.TEST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'],
'argumentCount' => '2',
],
'CHOOSE' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Selection::class, 'CHOOSE'],
'argumentCount' => '2+',
],
'CHOOSECOLS' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2+',
],
'CHOOSEROWS' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2+',
],
'CLEAN' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Trim::class, 'nonPrintable'],
'argumentCount' => '1',
],
'CODE' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\CharacterConvert::class, 'code'],
'argumentCount' => '1',
],
'COLUMN' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMN'],
'argumentCount' => '-1',
'passCellReference' => true,
'passByReference' => [true],
],
'COLUMNS' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMNS'],
'argumentCount' => '1',
],
'COMBIN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Combinations::class, 'withoutRepetition'],
'argumentCount' => '2',
],
'COMBINA' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Combinations::class, 'withRepetition'],
'argumentCount' => '2',
],
'COMPLEX' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\Complex::class, 'COMPLEX'],
'argumentCount' => '2,3',
],
'CONCAT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Concatenate::class, 'CONCATENATE'],
'argumentCount' => '1+',
],
'CONCATENATE' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Concatenate::class, 'CONCATENATE'],
'argumentCount' => '1+',
],
'CONFIDENCE' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'],
'argumentCount' => '3',
],
'CONFIDENCE.NORM' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'],
'argumentCount' => '3',
],
'CONFIDENCE.T' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3',
],
'CONVERT' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertUOM::class, 'CONVERT'],
'argumentCount' => '3',
],
'CORREL' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'CORREL'],
'argumentCount' => '2',
],
'COS' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Cosine::class, 'cos'],
'argumentCount' => '1',
],
'COSH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Cosine::class, 'cosh'],
'argumentCount' => '1',
],
'COT' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Cotangent::class, 'cot'],
'argumentCount' => '1',
],
'COTH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Cotangent::class, 'coth'],
'argumentCount' => '1',
],
'COUNT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Counts::class, 'COUNT'],
'argumentCount' => '1+',
],
'COUNTA' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Counts::class, 'COUNTA'],
'argumentCount' => '1+',
],
'COUNTBLANK' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Counts::class, 'COUNTBLANK'],
'argumentCount' => '1',
],
'COUNTIF' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Conditional::class, 'COUNTIF'],
'argumentCount' => '2',
],
'COUNTIFS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Conditional::class, 'COUNTIFS'],
'argumentCount' => '2+',
],
'COUPDAYBS' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Coupons::class, 'COUPDAYBS'],
'argumentCount' => '3,4',
],
'COUPDAYS' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Coupons::class, 'COUPDAYS'],
'argumentCount' => '3,4',
],
'COUPDAYSNC' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Coupons::class, 'COUPDAYSNC'],
'argumentCount' => '3,4',
],
'COUPNCD' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Coupons::class, 'COUPNCD'],
'argumentCount' => '3,4',
],
'COUPNUM' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Coupons::class, 'COUPNUM'],
'argumentCount' => '3,4',
],
'COUPPCD' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Coupons::class, 'COUPPCD'],
'argumentCount' => '3,4',
],
'COVAR' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'COVAR'],
'argumentCount' => '2',
],
'COVARIANCE.P' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'COVAR'],
'argumentCount' => '2',
],
'COVARIANCE.S' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2',
],
'CRITBINOM' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'],
'argumentCount' => '3',
],
'CSC' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Cosecant::class, 'csc'],
'argumentCount' => '1',
],
'CSCH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Cosecant::class, 'csch'],
'argumentCount' => '1',
],
'CUBEKPIMEMBER' => [
'category' => Category::CATEGORY_CUBE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'CUBEMEMBER' => [
'category' => Category::CATEGORY_CUBE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'CUBEMEMBERPROPERTY' => [
'category' => Category::CATEGORY_CUBE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'CUBERANKEDMEMBER' => [
'category' => Category::CATEGORY_CUBE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'CUBESET' => [
'category' => Category::CATEGORY_CUBE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'CUBESETCOUNT' => [
'category' => Category::CATEGORY_CUBE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'CUBEVALUE' => [
'category' => Category::CATEGORY_CUBE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'CUMIPMT' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'interest'],
'argumentCount' => '6',
],
'CUMPRINC' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'principal'],
'argumentCount' => '6',
],
'DATE' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Date::class, 'fromYMD'],
'argumentCount' => '3',
],
'DATEDIF' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Difference::class, 'interval'],
'argumentCount' => '2,3',
],
'DATESTRING' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'DATEVALUE' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\DateValue::class, 'fromString'],
'argumentCount' => '1',
],
'DAVERAGE' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DAverage::class, 'evaluate'],
'argumentCount' => '3',
],
'DAY' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\DateParts::class, 'day'],
'argumentCount' => '1',
],
'DAYS' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Days::class, 'between'],
'argumentCount' => '2',
],
'DAYS360' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Days360::class, 'between'],
'argumentCount' => '2,3',
],
'DB' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Depreciation::class, 'DB'],
'argumentCount' => '4,5',
],
'DBCS' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1',
],
'DCOUNT' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DCount::class, 'evaluate'],
'argumentCount' => '3',
],
'DCOUNTA' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DCountA::class, 'evaluate'],
'argumentCount' => '3',
],
'DDB' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Depreciation::class, 'DDB'],
'argumentCount' => '4,5',
],
'DEC2BIN' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertDecimal::class, 'toBinary'],
'argumentCount' => '1,2',
],
'DEC2HEX' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertDecimal::class, 'toHex'],
'argumentCount' => '1,2',
],
'DEC2OCT' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertDecimal::class, 'toOctal'],
'argumentCount' => '1,2',
],
'DECIMAL' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2',
],
'DEGREES' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Angle::class, 'toDegrees'],
'argumentCount' => '1',
],
'DELTA' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\Compare::class, 'DELTA'],
'argumentCount' => '1,2',
],
'DEVSQ' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Deviations::class, 'sumSquares'],
'argumentCount' => '1+',
],
'DGET' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DGet::class, 'evaluate'],
'argumentCount' => '3',
],
'DISC' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities\Rates::class, 'discount'],
'argumentCount' => '4,5',
],
'DMAX' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DMax::class, 'evaluate'],
'argumentCount' => '3',
],
'DMIN' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DMin::class, 'evaluate'],
'argumentCount' => '3',
],
'DOLLAR' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Format::class, 'DOLLAR'],
'argumentCount' => '1,2',
],
'DOLLARDE' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Dollar::class, 'decimal'],
'argumentCount' => '2',
],
'DOLLARFR' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Dollar::class, 'fractional'],
'argumentCount' => '2',
],
'DPRODUCT' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DProduct::class, 'evaluate'],
'argumentCount' => '3',
],
'DROP' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2-3',
],
'DSTDEV' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DStDev::class, 'evaluate'],
'argumentCount' => '3',
],
'DSTDEVP' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DStDevP::class, 'evaluate'],
'argumentCount' => '3',
],
'DSUM' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DSum::class, 'evaluate'],
'argumentCount' => '3',
],
'DURATION' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '5,6',
],
'DVAR' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DVar::class, 'evaluate'],
'argumentCount' => '3',
],
'DVARP' => [
'category' => Category::CATEGORY_DATABASE,
'functionCall' => [Database\DVarP::class, 'evaluate'],
'argumentCount' => '3',
],
'ECMA.CEILING' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1,2',
],
'EDATE' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Month::class, 'adjust'],
'argumentCount' => '2',
],
'EFFECT' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\InterestRate::class, 'effective'],
'argumentCount' => '2',
],
'ENCODEURL' => [
'category' => Category::CATEGORY_WEB,
'functionCall' => [Web\Service::class, 'urlEncode'],
'argumentCount' => '1',
],
'EOMONTH' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Month::class, 'lastDay'],
'argumentCount' => '2',
],
'ERF' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\Erf::class, 'ERF'],
'argumentCount' => '1,2',
],
'ERF.PRECISE' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\Erf::class, 'ERFPRECISE'],
'argumentCount' => '1',
],
'ERFC' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ErfC::class, 'ERFC'],
'argumentCount' => '1',
],
'ERFC.PRECISE' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ErfC::class, 'ERFC'],
'argumentCount' => '1',
],
'ERROR.TYPE' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [ExcelError::class, 'type'],
'argumentCount' => '1',
],
'EVEN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Round::class, 'even'],
'argumentCount' => '1',
],
'EXACT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Text::class, 'exact'],
'argumentCount' => '2',
],
'EXP' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Exp::class, 'evaluate'],
'argumentCount' => '1',
],
'EXPAND' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2-4',
],
'EXPONDIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'],
'argumentCount' => '3',
],
'EXPON.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'],
'argumentCount' => '3',
],
'FACT' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Factorial::class, 'fact'],
'argumentCount' => '1',
],
'FACTDOUBLE' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Factorial::class, 'factDouble'],
'argumentCount' => '1',
],
'FALSE' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical\Boolean::class, 'FALSE'],
'argumentCount' => '0',
],
'FDIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3',
],
'F.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\F::class, 'distribution'],
'argumentCount' => '4',
],
'F.DIST.RT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3',
],
'FILTER' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Filter::class, 'filter'],
'argumentCount' => '2-3',
],
'FILTERXML' => [
'category' => Category::CATEGORY_WEB,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2',
],
'FIND' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Search::class, 'sensitive'],
'argumentCount' => '2,3',
],
'FINDB' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Search::class, 'sensitive'],
'argumentCount' => '2,3',
],
'FINV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3',
],
'F.INV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3',
],
'F.INV.RT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3',
],
'FISHER' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Fisher::class, 'distribution'],
'argumentCount' => '1',
],
'FISHERINV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Fisher::class, 'inverse'],
'argumentCount' => '1',
],
'FIXED' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Format::class, 'FIXEDFORMAT'],
'argumentCount' => '1-3',
],
'FLOOR' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Floor::class, 'floor'],
'argumentCount' => '1-2', // Excel requries 2, Ods/Gnumeric 1-2
],
'FLOOR.MATH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Floor::class, 'math'],
'argumentCount' => '1-3',
],
'FLOOR.PRECISE' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Floor::class, 'precise'],
'argumentCount' => '1-2',
],
'FORECAST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'FORECAST'],
'argumentCount' => '3',
],
'FORECAST.ETS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3-6',
],
'FORECAST.ETS.CONFINT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3-6',
],
'FORECAST.ETS.SEASONALITY' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2-4',
],
'FORECAST.ETS.STAT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3-6',
],
'FORECAST.LINEAR' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'FORECAST'],
'argumentCount' => '3',
],
'FORMULATEXT' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Formula::class, 'text'],
'argumentCount' => '1',
'passCellReference' => true,
'passByReference' => [true],
],
'FREQUENCY' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2',
],
'FTEST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2',
],
'F.TEST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2',
],
'FV' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'futureValue'],
'argumentCount' => '3-5',
],
'FVSCHEDULE' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Single::class, 'futureValue'],
'argumentCount' => '2',
],
'GAMMA' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Gamma::class, 'gamma'],
'argumentCount' => '1',
],
'GAMMADIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'],
'argumentCount' => '4',
],
'GAMMA.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'],
'argumentCount' => '4',
],
'GAMMAINV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'],
'argumentCount' => '3',
],
'GAMMA.INV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'],
'argumentCount' => '3',
],
'GAMMALN' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'],
'argumentCount' => '1',
],
'GAMMALN.PRECISE' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'],
'argumentCount' => '1',
],
'GAUSS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'gauss'],
'argumentCount' => '1',
],
'GCD' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Gcd::class, 'evaluate'],
'argumentCount' => '1+',
],
'GEOMEAN' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Averages\Mean::class, 'geometric'],
'argumentCount' => '1+',
],
'GESTEP' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\Compare::class, 'GESTEP'],
'argumentCount' => '1,2',
],
'GETPIVOTDATA' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2+',
],
'GROWTH' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'GROWTH'],
'argumentCount' => '1-4',
],
'HARMEAN' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Averages\Mean::class, 'harmonic'],
'argumentCount' => '1+',
],
'HEX2BIN' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertHex::class, 'toBinary'],
'argumentCount' => '1,2',
],
'HEX2DEC' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertHex::class, 'toDecimal'],
'argumentCount' => '1',
],
'HEX2OCT' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertHex::class, 'toOctal'],
'argumentCount' => '1,2',
],
'HLOOKUP' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\HLookup::class, 'lookup'],
'argumentCount' => '3,4',
],
'HOUR' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\TimeParts::class, 'hour'],
'argumentCount' => '1',
],
'HSTACK' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1+',
],
'HYPERLINK' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Hyperlink::class, 'set'],
'argumentCount' => '1,2',
'passCellReference' => true,
],
'HYPGEOMDIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\HyperGeometric::class, 'distribution'],
'argumentCount' => '4',
],
'HYPGEOM.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '5',
],
'IF' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical\Conditional::class, 'statementIf'],
'argumentCount' => '2-3',
],
'IFERROR' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical\Conditional::class, 'IFERROR'],
'argumentCount' => '2',
],
'IFNA' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical\Conditional::class, 'IFNA'],
'argumentCount' => '2',
],
'IFS' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical\Conditional::class, 'IFS'],
'argumentCount' => '2+',
],
'IMABS' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMABS'],
'argumentCount' => '1',
],
'IMAGINARY' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\Complex::class, 'IMAGINARY'],
'argumentCount' => '1',
],
'IMARGUMENT' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMARGUMENT'],
'argumentCount' => '1',
],
'IMCONJUGATE' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMCONJUGATE'],
'argumentCount' => '1',
],
'IMCOS' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOS'],
'argumentCount' => '1',
],
'IMCOSH' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOSH'],
'argumentCount' => '1',
],
'IMCOT' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOT'],
'argumentCount' => '1',
],
'IMCSC' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSC'],
'argumentCount' => '1',
],
'IMCSCH' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSCH'],
'argumentCount' => '1',
],
'IMDIV' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexOperations::class, 'IMDIV'],
'argumentCount' => '2',
],
'IMEXP' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMEXP'],
'argumentCount' => '1',
],
'IMLN' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMLN'],
'argumentCount' => '1',
],
'IMLOG10' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG10'],
'argumentCount' => '1',
],
'IMLOG2' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG2'],
'argumentCount' => '1',
],
'IMPOWER' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMPOWER'],
'argumentCount' => '2',
],
'IMPRODUCT' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexOperations::class, 'IMPRODUCT'],
'argumentCount' => '1+',
],
'IMREAL' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\Complex::class, 'IMREAL'],
'argumentCount' => '1',
],
'IMSEC' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMSEC'],
'argumentCount' => '1',
],
'IMSECH' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMSECH'],
'argumentCount' => '1',
],
'IMSIN' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMSIN'],
'argumentCount' => '1',
],
'IMSINH' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMSINH'],
'argumentCount' => '1',
],
'IMSQRT' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMSQRT'],
'argumentCount' => '1',
],
'IMSUB' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexOperations::class, 'IMSUB'],
'argumentCount' => '2',
],
'IMSUM' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexOperations::class, 'IMSUM'],
'argumentCount' => '1+',
],
'IMTAN' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ComplexFunctions::class, 'IMTAN'],
'argumentCount' => '1',
],
'INDEX' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Matrix::class, 'index'],
'argumentCount' => '2-4',
],
'INDIRECT' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Indirect::class, 'INDIRECT'],
'argumentCount' => '1,2',
'passCellReference' => true,
],
'INFO' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1',
],
'INT' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\IntClass::class, 'evaluate'],
'argumentCount' => '1',
],
'INTERCEPT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'INTERCEPT'],
'argumentCount' => '2',
],
'INTRATE' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities\Rates::class, 'interest'],
'argumentCount' => '4,5',
],
'IPMT' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'payment'],
'argumentCount' => '4-6',
],
'IRR' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'rate'],
'argumentCount' => '1,2',
],
'ISBLANK' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\Value::class, 'isBlank'],
'argumentCount' => '1',
],
'ISERR' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\ErrorValue::class, 'isErr'],
'argumentCount' => '1',
],
'ISERROR' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\ErrorValue::class, 'isError'],
'argumentCount' => '1',
],
'ISEVEN' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\Value::class, 'isEven'],
'argumentCount' => '1',
],
'ISFORMULA' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\Value::class, 'isFormula'],
'argumentCount' => '1',
'passCellReference' => true,
'passByReference' => [true],
],
'ISLOGICAL' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\Value::class, 'isLogical'],
'argumentCount' => '1',
],
'ISNA' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\ErrorValue::class, 'isNa'],
'argumentCount' => '1',
],
'ISNONTEXT' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\Value::class, 'isNonText'],
'argumentCount' => '1',
],
'ISNUMBER' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\Value::class, 'isNumber'],
'argumentCount' => '1',
],
'ISO.CEILING' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1,2',
],
'ISODD' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\Value::class, 'isOdd'],
'argumentCount' => '1',
],
'ISOMITTED' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '*',
],
'ISOWEEKNUM' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Week::class, 'isoWeekNumber'],
'argumentCount' => '1',
],
'ISPMT' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'schedulePayment'],
'argumentCount' => '4',
],
'ISREF' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\Value::class, 'isRef'],
'argumentCount' => '1',
'passCellReference' => true,
'passByReference' => [true],
],
'ISTEXT' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\Value::class, 'isText'],
'argumentCount' => '1',
],
'ISTHAIDIGIT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'JIS' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1',
],
'KURT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Deviations::class, 'kurtosis'],
'argumentCount' => '1+',
],
'LAMBDA' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '*',
],
'LARGE' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Size::class, 'large'],
'argumentCount' => '2',
],
'LCM' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Lcm::class, 'evaluate'],
'argumentCount' => '1+',
],
'LEFT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Extract::class, 'left'],
'argumentCount' => '1,2',
],
'LEFTB' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Extract::class, 'left'],
'argumentCount' => '1,2',
],
'LEN' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Text::class, 'length'],
'argumentCount' => '1',
],
'LENB' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Text::class, 'length'],
'argumentCount' => '1',
],
'LET' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '*',
],
'LINEST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'LINEST'],
'argumentCount' => '1-4',
],
'LN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Logarithms::class, 'natural'],
'argumentCount' => '1',
],
'LOG' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Logarithms::class, 'withBase'],
'argumentCount' => '1,2',
],
'LOG10' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Logarithms::class, 'base10'],
'argumentCount' => '1',
],
'LOGEST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'LOGEST'],
'argumentCount' => '1-4',
],
'LOGINV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'],
'argumentCount' => '3',
],
'LOGNORMDIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\LogNormal::class, 'cumulative'],
'argumentCount' => '3',
],
'LOGNORM.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\LogNormal::class, 'distribution'],
'argumentCount' => '4',
],
'LOGNORM.INV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'],
'argumentCount' => '3',
],
'LOOKUP' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Lookup::class, 'lookup'],
'argumentCount' => '2,3',
],
'LOWER' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\CaseConvert::class, 'lower'],
'argumentCount' => '1',
],
'MAKEARRAY' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '*',
],
'MAP' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '*',
],
'MATCH' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\ExcelMatch::class, 'MATCH'],
'argumentCount' => '2,3',
],
'MAX' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Maximum::class, 'max'],
'argumentCount' => '1+',
],
'MAXA' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Maximum::class, 'maxA'],
'argumentCount' => '1+',
],
'MAXIFS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Conditional::class, 'MAXIFS'],
'argumentCount' => '3+',
],
'MDETERM' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\MatrixFunctions::class, 'determinant'],
'argumentCount' => '1',
],
'MDURATION' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '5,6',
],
'MEDIAN' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Averages::class, 'median'],
'argumentCount' => '1+',
],
'MEDIANIF' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2+',
],
'MID' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Extract::class, 'mid'],
'argumentCount' => '3',
],
'MIDB' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Extract::class, 'mid'],
'argumentCount' => '3',
],
'MIN' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Minimum::class, 'min'],
'argumentCount' => '1+',
],
'MINA' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Minimum::class, 'minA'],
'argumentCount' => '1+',
],
'MINIFS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Conditional::class, 'MINIFS'],
'argumentCount' => '3+',
],
'MINUTE' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\TimeParts::class, 'minute'],
'argumentCount' => '1',
],
'MINVERSE' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\MatrixFunctions::class, 'inverse'],
'argumentCount' => '1',
],
'MIRR' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'modifiedRate'],
'argumentCount' => '3',
],
'MMULT' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\MatrixFunctions::class, 'multiply'],
'argumentCount' => '2',
],
'MOD' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Operations::class, 'mod'],
'argumentCount' => '2',
],
'MODE' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Averages::class, 'mode'],
'argumentCount' => '1+',
],
'MODE.MULT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1+',
],
'MODE.SNGL' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Averages::class, 'mode'],
'argumentCount' => '1+',
],
'MONTH' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\DateParts::class, 'month'],
'argumentCount' => '1',
],
'MROUND' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Round::class, 'multiple'],
'argumentCount' => '2',
],
'MULTINOMIAL' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Factorial::class, 'multinomial'],
'argumentCount' => '1+',
],
'MUNIT' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\MatrixFunctions::class, 'identity'],
'argumentCount' => '1',
],
'N' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\Value::class, 'asNumber'],
'argumentCount' => '1',
],
'NA' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [ExcelError::class, 'NA'],
'argumentCount' => '0',
],
'NEGBINOMDIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Binomial::class, 'negative'],
'argumentCount' => '3',
],
'NEGBINOM.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '4',
],
'NETWORKDAYS' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\NetworkDays::class, 'count'],
'argumentCount' => '2-3',
],
'NETWORKDAYS.INTL' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2-4',
],
'NOMINAL' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\InterestRate::class, 'nominal'],
'argumentCount' => '2',
],
'NORMDIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'],
'argumentCount' => '4',
],
'NORM.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'],
'argumentCount' => '4',
],
'NORMINV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'],
'argumentCount' => '3',
],
'NORM.INV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'],
'argumentCount' => '3',
],
'NORMSDIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'cumulative'],
'argumentCount' => '1',
],
'NORM.S.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'distribution'],
'argumentCount' => '1,2',
],
'NORMSINV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'],
'argumentCount' => '1',
],
'NORM.S.INV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'],
'argumentCount' => '1',
],
'NOT' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical\Operations::class, 'NOT'],
'argumentCount' => '1',
],
'NOW' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Current::class, 'now'],
'argumentCount' => '0',
],
'NPER' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'periods'],
'argumentCount' => '3-5',
],
'NPV' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'presentValue'],
'argumentCount' => '2+',
],
'NUMBERSTRING' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'NUMBERVALUE' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Format::class, 'NUMBERVALUE'],
'argumentCount' => '1+',
],
'OCT2BIN' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertOctal::class, 'toBinary'],
'argumentCount' => '1,2',
],
'OCT2DEC' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertOctal::class, 'toDecimal'],
'argumentCount' => '1',
],
'OCT2HEX' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering\ConvertOctal::class, 'toHex'],
'argumentCount' => '1,2',
],
'ODD' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Round::class, 'odd'],
'argumentCount' => '1',
],
'ODDFPRICE' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '8,9',
],
'ODDFYIELD' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '8,9',
],
'ODDLPRICE' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '7,8',
],
'ODDLYIELD' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '7,8',
],
'OFFSET' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Offset::class, 'OFFSET'],
'argumentCount' => '3-5',
'passCellReference' => true,
'passByReference' => [true],
],
'OR' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical\Operations::class, 'logicalOr'],
'argumentCount' => '1+',
],
'PDURATION' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Single::class, 'periods'],
'argumentCount' => '3',
],
'PEARSON' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'CORREL'],
'argumentCount' => '2',
],
'PERCENTILE' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'],
'argumentCount' => '2',
],
'PERCENTILE.EXC' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2',
],
'PERCENTILE.INC' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'],
'argumentCount' => '2',
],
'PERCENTRANK' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'],
'argumentCount' => '2,3',
],
'PERCENTRANK.EXC' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2,3',
],
'PERCENTRANK.INC' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'],
'argumentCount' => '2,3',
],
'PERMUT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Permutations::class, 'PERMUT'],
'argumentCount' => '2',
],
'PERMUTATIONA' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Permutations::class, 'PERMUTATIONA'],
'argumentCount' => '2',
],
'PHONETIC' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1',
],
'PHI' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1',
],
'PI' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => 'pi',
'argumentCount' => '0',
],
'PMT' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'annuity'],
'argumentCount' => '3-5',
],
'POISSON' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'],
'argumentCount' => '3',
],
'POISSON.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'],
'argumentCount' => '3',
],
'POWER' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Operations::class, 'power'],
'argumentCount' => '2',
],
'PPMT' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'interestPayment'],
'argumentCount' => '4-6',
],
'PRICE' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities\Price::class, 'price'],
'argumentCount' => '6,7',
],
'PRICEDISC' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities\Price::class, 'priceDiscounted'],
'argumentCount' => '4,5',
],
'PRICEMAT' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities\Price::class, 'priceAtMaturity'],
'argumentCount' => '5,6',
],
'PROB' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3,4',
],
'PRODUCT' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Operations::class, 'product'],
'argumentCount' => '1+',
],
'PROPER' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\CaseConvert::class, 'proper'],
'argumentCount' => '1',
],
'PV' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'presentValue'],
'argumentCount' => '3-5',
],
'QUARTILE' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'],
'argumentCount' => '2',
],
'QUARTILE.EXC' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2',
],
'QUARTILE.INC' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'],
'argumentCount' => '2',
],
'QUOTIENT' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Operations::class, 'quotient'],
'argumentCount' => '2',
],
'RADIANS' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Angle::class, 'toRadians'],
'argumentCount' => '1',
],
'RAND' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Random::class, 'rand'],
'argumentCount' => '0',
],
'RANDARRAY' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Random::class, 'randArray'],
'argumentCount' => '0-5',
],
'RANDBETWEEN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Random::class, 'randBetween'],
'argumentCount' => '2',
],
'RANK' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Percentiles::class, 'RANK'],
'argumentCount' => '2,3',
],
'RANK.AVG' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2,3',
],
'RANK.EQ' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Percentiles::class, 'RANK'],
'argumentCount' => '2,3',
],
'RATE' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'rate'],
'argumentCount' => '3-6',
],
'RECEIVED' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities\Price::class, 'received'],
'argumentCount' => '4-5',
],
'REDUCE' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '*',
],
'REPLACE' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Replace::class, 'replace'],
'argumentCount' => '4',
],
'REPLACEB' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Replace::class, 'replace'],
'argumentCount' => '4',
],
'REPT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Concatenate::class, 'builtinREPT'],
'argumentCount' => '2',
],
'RIGHT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Extract::class, 'right'],
'argumentCount' => '1,2',
],
'RIGHTB' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Extract::class, 'right'],
'argumentCount' => '1,2',
],
'ROMAN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Roman::class, 'evaluate'],
'argumentCount' => '1,2',
],
'ROUND' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Round::class, 'round'],
'argumentCount' => '2',
],
'ROUNDBAHTDOWN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'ROUNDBAHTUP' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'ROUNDDOWN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Round::class, 'down'],
'argumentCount' => '2',
],
'ROUNDUP' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Round::class, 'up'],
'argumentCount' => '2',
],
'ROW' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\RowColumnInformation::class, 'ROW'],
'argumentCount' => '-1',
'passCellReference' => true,
'passByReference' => [true],
],
'ROWS' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\RowColumnInformation::class, 'ROWS'],
'argumentCount' => '1',
],
'RRI' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Single::class, 'interestRate'],
'argumentCount' => '3',
],
'RSQ' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'RSQ'],
'argumentCount' => '2',
],
'RTD' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1+',
],
'SEARCH' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Search::class, 'insensitive'],
'argumentCount' => '2,3',
],
'SCAN' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '*',
],
'SEARCHB' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Search::class, 'insensitive'],
'argumentCount' => '2,3',
],
'SEC' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Secant::class, 'sec'],
'argumentCount' => '1',
],
'SECH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Secant::class, 'sech'],
'argumentCount' => '1',
],
'SECOND' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\TimeParts::class, 'second'],
'argumentCount' => '1',
],
'SEQUENCE' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\MatrixFunctions::class, 'sequence'],
'argumentCount' => '1-4',
],
'SERIESSUM' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\SeriesSum::class, 'evaluate'],
'argumentCount' => '4',
],
'SHEET' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '0,1',
],
'SHEETS' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '0,1',
],
'SIGN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Sign::class, 'evaluate'],
'argumentCount' => '1',
],
'SIN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Sine::class, 'sin'],
'argumentCount' => '1',
],
'SINGLE' => [
'category' => Category::CATEGORY_UNCATEGORISED,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '*',
],
'SINH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Sine::class, 'sinh'],
'argumentCount' => '1',
],
'SKEW' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Deviations::class, 'skew'],
'argumentCount' => '1+',
],
'SKEW.P' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1+',
],
'SLN' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Depreciation::class, 'SLN'],
'argumentCount' => '3',
],
'SLOPE' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'SLOPE'],
'argumentCount' => '2',
],
'SMALL' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Size::class, 'small'],
'argumentCount' => '2',
],
'SORT' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Sort::class, 'sort'],
'argumentCount' => '1-4',
],
'SORTBY' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Sort::class, 'sortBy'],
'argumentCount' => '2+',
],
'SQRT' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Sqrt::class, 'sqrt'],
'argumentCount' => '1',
],
'SQRTPI' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Sqrt::class, 'pi'],
'argumentCount' => '1',
],
'STANDARDIZE' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Standardize::class, 'execute'],
'argumentCount' => '3',
],
'STDEV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'],
'argumentCount' => '1+',
],
'STDEV.S' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'],
'argumentCount' => '1+',
],
'STDEV.P' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'],
'argumentCount' => '1+',
],
'STDEVA' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\StandardDeviations::class, 'STDEVA'],
'argumentCount' => '1+',
],
'STDEVP' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'],
'argumentCount' => '1+',
],
'STDEVPA' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\StandardDeviations::class, 'STDEVPA'],
'argumentCount' => '1+',
],
'STEYX' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'STEYX'],
'argumentCount' => '2',
],
'SUBSTITUTE' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Replace::class, 'substitute'],
'argumentCount' => '3,4',
],
'SUBTOTAL' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Subtotal::class, 'evaluate'],
'argumentCount' => '2+',
'passCellReference' => true,
],
'SUM' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Sum::class, 'sumErroringStrings'],
'argumentCount' => '1+',
],
'SUMIF' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Statistical\Conditional::class, 'SUMIF'],
'argumentCount' => '2,3',
],
'SUMIFS' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Statistical\Conditional::class, 'SUMIFS'],
'argumentCount' => '3+',
],
'SUMPRODUCT' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Sum::class, 'product'],
'argumentCount' => '1+',
],
'SUMSQ' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\SumSquares::class, 'sumSquare'],
'argumentCount' => '1+',
],
'SUMX2MY2' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredMinusYSquared'],
'argumentCount' => '2',
],
'SUMX2PY2' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredPlusYSquared'],
'argumentCount' => '2',
],
'SUMXMY2' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\SumSquares::class, 'sumXMinusYSquared'],
'argumentCount' => '2',
],
'SWITCH' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical\Conditional::class, 'statementSwitch'],
'argumentCount' => '3+',
],
'SYD' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Depreciation::class, 'SYD'],
'argumentCount' => '4',
],
'T' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Text::class, 'test'],
'argumentCount' => '1',
],
'TAKE' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2-3',
],
'TAN' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Tangent::class, 'tan'],
'argumentCount' => '1',
],
'TANH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trig\Tangent::class, 'tanh'],
'argumentCount' => '1',
],
'TBILLEQ' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\TreasuryBill::class, 'bondEquivalentYield'],
'argumentCount' => '3',
],
'TBILLPRICE' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\TreasuryBill::class, 'price'],
'argumentCount' => '3',
],
'TBILLYIELD' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\TreasuryBill::class, 'yield'],
'argumentCount' => '3',
],
'TDIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\StudentT::class, 'distribution'],
'argumentCount' => '3',
],
'T.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3',
],
'T.DIST.2T' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2',
],
'T.DIST.RT' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2',
],
'TEXT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Format::class, 'TEXTFORMAT'],
'argumentCount' => '2',
],
'TEXTAFTER' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Extract::class, 'after'],
'argumentCount' => '2-6',
],
'TEXTBEFORE' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Extract::class, 'before'],
'argumentCount' => '2-6',
],
'TEXTJOIN' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Concatenate::class, 'TEXTJOIN'],
'argumentCount' => '3+',
],
'TEXTSPLIT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Text::class, 'split'],
'argumentCount' => '2-6',
],
'THAIDAYOFWEEK' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'THAIDIGIT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'THAIMONTHOFYEAR' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'THAINUMSOUND' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'THAINUMSTRING' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'THAISTRINGLENGTH' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'THAIYEAR' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '?',
],
'TIME' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Time::class, 'fromHMS'],
'argumentCount' => '3',
],
'TIMEVALUE' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\TimeValue::class, 'fromString'],
'argumentCount' => '1',
],
'TINV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'],
'argumentCount' => '2',
],
'T.INV' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'],
'argumentCount' => '2',
],
'T.INV.2T' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2',
],
'TODAY' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Current::class, 'today'],
'argumentCount' => '0',
],
'TOCOL' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1-3',
],
'TOROW' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1-3',
],
'TRANSPOSE' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Matrix::class, 'transpose'],
'argumentCount' => '1',
],
'TREND' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Trends::class, 'TREND'],
'argumentCount' => '1-4',
],
'TRIM' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Trim::class, 'spaces'],
'argumentCount' => '1',
],
'TRIMMEAN' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Averages\Mean::class, 'trim'],
'argumentCount' => '2',
],
'TRUE' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical\Boolean::class, 'TRUE'],
'argumentCount' => '0',
],
'TRUNC' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig\Trunc::class, 'evaluate'],
'argumentCount' => '1,2',
],
'TTEST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '4',
],
'T.TEST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '4',
],
'TYPE' => [
'category' => Category::CATEGORY_INFORMATION,
'functionCall' => [Information\Value::class, 'type'],
'argumentCount' => '1',
],
'UNICHAR' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\CharacterConvert::class, 'character'],
'argumentCount' => '1',
],
'UNICODE' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\CharacterConvert::class, 'code'],
'argumentCount' => '1',
],
'UNIQUE' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\Unique::class, 'unique'],
'argumentCount' => '1+',
],
'UPPER' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\CaseConvert::class, 'upper'],
'argumentCount' => '1',
],
'USDOLLAR' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Dollar::class, 'format'],
'argumentCount' => '2',
],
'VALUE' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Format::class, 'VALUE'],
'argumentCount' => '1',
],
'VALUETOTEXT' => [
'category' => Category::CATEGORY_TEXT_AND_DATA,
'functionCall' => [TextData\Format::class, 'valueToText'],
'argumentCount' => '1,2',
],
'VAR' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Variances::class, 'VAR'],
'argumentCount' => '1+',
],
'VAR.P' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Variances::class, 'VARP'],
'argumentCount' => '1+',
],
'VAR.S' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Variances::class, 'VAR'],
'argumentCount' => '1+',
],
'VARA' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Variances::class, 'VARA'],
'argumentCount' => '1+',
],
'VARP' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Variances::class, 'VARP'],
'argumentCount' => '1+',
],
'VARPA' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Variances::class, 'VARPA'],
'argumentCount' => '1+',
],
'VDB' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '5-7',
],
'VLOOKUP' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [LookupRef\VLookup::class, 'lookup'],
'argumentCount' => '3,4',
],
'VSTACK' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1+',
],
'WEBSERVICE' => [
'category' => Category::CATEGORY_WEB,
'functionCall' => [Web\Service::class, 'webService'],
'argumentCount' => '1',
],
'WEEKDAY' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Week::class, 'day'],
'argumentCount' => '1,2',
],
'WEEKNUM' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\Week::class, 'number'],
'argumentCount' => '1,2',
],
'WEIBULL' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'],
'argumentCount' => '4',
],
'WEIBULL.DIST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'],
'argumentCount' => '4',
],
'WORKDAY' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\WorkDay::class, 'date'],
'argumentCount' => '2-3',
],
'WORKDAY.INTL' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2-4',
],
'WRAPCOLS' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2-3',
],
'WRAPROWS' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2-3',
],
'XIRR' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class, 'rate'],
'argumentCount' => '2,3',
],
'XLOOKUP' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '3-6',
],
'XNPV' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class, 'presentValue'],
'argumentCount' => '3',
],
'XMATCH' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2,3',
],
'XOR' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical\Operations::class, 'logicalXor'],
'argumentCount' => '1+',
],
'YEAR' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\DateParts::class, 'year'],
'argumentCount' => '1',
],
'YEARFRAC' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTimeExcel\YearFrac::class, 'fraction'],
'argumentCount' => '2,3',
],
'YIELD' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '6,7',
],
'YIELDDISC' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities\Yields::class, 'yieldDiscounted'],
'argumentCount' => '4,5',
],
'YIELDMAT' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities\Yields::class, 'yieldAtMaturity'],
'argumentCount' => '5,6',
],
'ZTEST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'],
'argumentCount' => '2-3',
],
'Z.TEST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'],
'argumentCount' => '2-3',
],
];
/**
* Internal functions used for special control purposes.
*/
private static array $controlFunctions = [
'MKMATRIX' => [
'argumentCount' => '*',
'functionCall' => [Internal\MakeMatrix::class, 'make'],
],
'NAME.ERROR' => [
'argumentCount' => '*',
'functionCall' => [ExcelError::class, 'NAME'],
],
'WILDCARDMATCH' => [
'argumentCount' => '2',
'functionCall' => [Internal\WildcardMatch::class, 'compare'],
],
];
public function __construct(?Spreadsheet $spreadsheet = null)
{
$this->spreadsheet = $spreadsheet;
$this->cyclicReferenceStack = new CyclicReferenceStack();
$this->debugLog = new Logger($this->cyclicReferenceStack);
$this->branchPruner = new BranchPruner($this->branchPruningEnabled);
self::$referenceHelper = ReferenceHelper::getInstance();
}
private static function loadLocales(): void
{
$localeFileDirectory = __DIR__ . '/locale/';
$localeFileNames = glob($localeFileDirectory . '*', GLOB_ONLYDIR) ?: [];
foreach ($localeFileNames as $filename) {
$filename = substr($filename, strlen($localeFileDirectory));
if ($filename != 'en') {
self::$validLocaleLanguages[] = $filename;
}
}
}
/**
* Get an instance of this class.
*
* @param ?Spreadsheet $spreadsheet Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object,
* or NULL to create a standalone calculation engine
*/
public static function getInstance(?Spreadsheet $spreadsheet = null): self
{
if ($spreadsheet !== null) {
$instance = $spreadsheet->getCalculationEngine();
if (isset($instance)) {
return $instance;
}
}
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Flush the calculation cache for any existing instance of this class
* but only if a Calculation instance exists.
*/
public function flushInstance(): void
{
$this->clearCalculationCache();
$this->branchPruner->clearBranchStore();
}
/**
* Get the Logger for this calculation engine instance.
*/
public function getDebugLog(): Logger
{
return $this->debugLog;
}
/**
* __clone implementation. Cloning should not be allowed in a Singleton!
*/
final public function __clone()
{
throw new Exception('Cloning the calculation engine is not allowed!');
}
/**
* Return the locale-specific translation of TRUE.
*
* @return string locale-specific translation of TRUE
*/
public static function getTRUE(): string
{
return self::$localeBoolean['TRUE'];
}
/**
* Return the locale-specific translation of FALSE.
*
* @return string locale-specific translation of FALSE
*/
public static function getFALSE(): string
{
return self::$localeBoolean['FALSE'];
}
/**
* Set the Array Return Type (Array or Value of first element in the array).
*
* @param string $returnType Array return type
*
* @return bool Success or failure
*/
public static function setArrayReturnType(string $returnType): bool
{
if (
($returnType == self::RETURN_ARRAY_AS_VALUE)
|| ($returnType == self::RETURN_ARRAY_AS_ERROR)
|| ($returnType == self::RETURN_ARRAY_AS_ARRAY)
) {
self::$returnArrayAsType = $returnType;
return true;
}
return false;
}
/**
* Return the Array Return Type (Array or Value of first element in the array).
*
* @return string $returnType Array return type
*/
public static function getArrayReturnType(): string
{
return self::$returnArrayAsType;
}
/**
* Is calculation caching enabled?
*/
public function getCalculationCacheEnabled(): bool
{
return $this->calculationCacheEnabled;
}
/**
* Enable/disable calculation cache.
*/
public function setCalculationCacheEnabled(bool $calculationCacheEnabled): void
{
$this->calculationCacheEnabled = $calculationCacheEnabled;
$this->clearCalculationCache();
}
/**
* Enable calculation cache.
*/
public function enableCalculationCache(): void
{
$this->setCalculationCacheEnabled(true);
}
/**
* Disable calculation cache.
*/
public function disableCalculationCache(): void
{
$this->setCalculationCacheEnabled(false);
}
/**
* Clear calculation cache.
*/
public function clearCalculationCache(): void
{
$this->calculationCache = [];
}
/**
* Clear calculation cache for a specified worksheet.
*/
public function clearCalculationCacheForWorksheet(string $worksheetName): void
{
if (isset($this->calculationCache[$worksheetName])) {
unset($this->calculationCache[$worksheetName]);
}
}
/**
* Rename calculation cache for a specified worksheet.
*/
public function renameCalculationCacheForWorksheet(string $fromWorksheetName, string $toWorksheetName): void
{
if (isset($this->calculationCache[$fromWorksheetName])) {
$this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName];
unset($this->calculationCache[$fromWorksheetName]);
}
}
/**
* Enable/disable calculation cache.
*/
public function setBranchPruningEnabled(mixed $enabled): void
{
$this->branchPruningEnabled = $enabled;
$this->branchPruner = new BranchPruner($this->branchPruningEnabled);
}
public function enableBranchPruning(): void
{
$this->setBranchPruningEnabled(true);
}
public function disableBranchPruning(): void
{
$this->setBranchPruningEnabled(false);
}
/**
* Get the currently defined locale code.
*/
public function getLocale(): string
{
return self::$localeLanguage;
}
private function getLocaleFile(string $localeDir, string $locale, string $language, string $file): string
{
$localeFileName = $localeDir . str_replace('_', DIRECTORY_SEPARATOR, $locale)
. DIRECTORY_SEPARATOR . $file;
if (!file_exists($localeFileName)) {
// If there isn't a locale specific file, look for a language specific file
$localeFileName = $localeDir . $language . DIRECTORY_SEPARATOR . $file;
if (!file_exists($localeFileName)) {
throw new Exception('Locale file not found');
}
}
return $localeFileName;
}
/**
* Set the locale code.
*
* @param string $locale The locale to use for formula translation, eg: 'en_us'
*/
public function setLocale(string $locale): bool
{
// Identify our locale and language
$language = $locale = strtolower($locale);
if (str_contains($locale, '_')) {
[$language] = explode('_', $locale);
}
if (count(self::$validLocaleLanguages) == 1) {
self::loadLocales();
}
// Test whether we have any language data for this language (any locale)
if (in_array($language, self::$validLocaleLanguages, true)) {
// initialise language/locale settings
self::$localeFunctions = [];
self::$localeArgumentSeparator = ',';
self::$localeBoolean = ['TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL'];
// Default is US English, if user isn't requesting US english, then read the necessary data from the locale files
if ($locale !== 'en_us') {
$localeDir = implode(DIRECTORY_SEPARATOR, [__DIR__, 'locale', null]);
// Search for a file with a list of function names for locale
try {
$functionNamesFile = $this->getLocaleFile($localeDir, $locale, $language, 'functions');
} catch (Exception $e) {
return false;
}
// Retrieve the list of locale or language specific function names
$localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
foreach ($localeFunctions as $localeFunction) {
[$localeFunction] = explode('##', $localeFunction); // Strip out comments
if (str_contains($localeFunction, '=')) {
[$fName, $lfName] = array_map('trim', explode('=', $localeFunction));
if ((str_starts_with($fName, '*') || isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) {
self::$localeFunctions[$fName] = $lfName;
}
}
}
// Default the TRUE and FALSE constants to the locale names of the TRUE() and FALSE() functions
if (isset(self::$localeFunctions['TRUE'])) {
self::$localeBoolean['TRUE'] = self::$localeFunctions['TRUE'];
}
if (isset(self::$localeFunctions['FALSE'])) {
self::$localeBoolean['FALSE'] = self::$localeFunctions['FALSE'];
}
try {
$configFile = $this->getLocaleFile($localeDir, $locale, $language, 'config');
} catch (Exception) {
return false;
}
$localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
foreach ($localeSettings as $localeSetting) {
[$localeSetting] = explode('##', $localeSetting); // Strip out comments
if (str_contains($localeSetting, '=')) {
[$settingName, $settingValue] = array_map('trim', explode('=', $localeSetting));
$settingName = strtoupper($settingName);
if ($settingValue !== '') {
switch ($settingName) {
case 'ARGUMENTSEPARATOR':
self::$localeArgumentSeparator = $settingValue;
break;
}
}
}
}
}
self::$functionReplaceFromExcel = self::$functionReplaceToExcel
= self::$functionReplaceFromLocale = self::$functionReplaceToLocale = null;
self::$localeLanguage = $locale;
return true;
}
return false;
}
public static function translateSeparator(
string $fromSeparator,
string $toSeparator,
string $formula,
int &$inBracesLevel,
string $openBrace = self::FORMULA_OPEN_FUNCTION_BRACE,
string $closeBrace = self::FORMULA_CLOSE_FUNCTION_BRACE
): string {
$strlen = mb_strlen($formula);
for ($i = 0; $i < $strlen; ++$i) {
$chr = mb_substr($formula, $i, 1);
switch ($chr) {
case $openBrace:
++$inBracesLevel;
break;
case $closeBrace:
--$inBracesLevel;
break;
case $fromSeparator:
if ($inBracesLevel > 0) {
$formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
}
}
}
return $formula;
}
private static function translateFormulaBlock(
array $from,
array $to,
string $formula,
int &$inFunctionBracesLevel,
int &$inMatrixBracesLevel,
string $fromSeparator,
string $toSeparator
): string {
// Function Names
$formula = (string) preg_replace($from, $to, $formula);
// Temporarily adjust matrix separators so that they won't be confused with function arguments
$formula = self::translateSeparator(';', '|', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
$formula = self::translateSeparator(',', '!', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
// Function Argument Separators
$formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inFunctionBracesLevel);
// Restore matrix separators
$formula = self::translateSeparator('|', ';', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
$formula = self::translateSeparator('!', ',', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
return $formula;
}
private static function translateFormula(array $from, array $to, string $formula, string $fromSeparator, string $toSeparator): string
{
// Convert any Excel function names and constant names to the required language;
// and adjust function argument separators
if (self::$localeLanguage !== 'en_us') {
$inFunctionBracesLevel = 0;
$inMatrixBracesLevel = 0;
// If there is the possibility of separators within a quoted string, then we treat them as literals
if (str_contains($formula, self::FORMULA_STRING_QUOTE)) {
// 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(self::FORMULA_STRING_QUOTE, $formula);
$notWithinQuotes = false;
foreach ($temp as &$value) {
// Only adjust in alternating array entries
$notWithinQuotes = $notWithinQuotes === false;
if ($notWithinQuotes === true) {
$value = self::translateFormulaBlock($from, $to, $value, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator);
}
}
unset($value);
// Then rebuild the formula string
$formula = implode(self::FORMULA_STRING_QUOTE, $temp);
} else {
// If there's no quoted strings, then we do a simple count/replace
$formula = self::translateFormulaBlock($from, $to, $formula, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator);
}
}
return $formula;
}
/** @var ?array */
private static ?array $functionReplaceFromExcel;
/** @var ?array */
private static ?array $functionReplaceToLocale;
/**
* @deprecated 1.30.0 use translateFormulaToLocale() instead
*
* @codeCoverageIgnore
*/
public function _translateFormulaToLocale(string $formula): string
{
return $this->translateFormulaToLocale($formula);
}
public function translateFormulaToLocale(string $formula): string
{
$formula = preg_replace(self::CALCULATION_REGEXP_STRIP_XLFN_XLWS, '', $formula) ?? '';
// Build list of function names and constants for translation
if (self::$functionReplaceFromExcel === null) {
self::$functionReplaceFromExcel = [];
foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName, '/') . '([\s]*\()/ui';
}
foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/ui';
}
}
if (self::$functionReplaceToLocale === null) {
self::$functionReplaceToLocale = [];
foreach (self::$localeFunctions as $localeFunctionName) {
self::$functionReplaceToLocale[] = '$1' . trim($localeFunctionName) . '$2';
}
foreach (self::$localeBoolean as $localeBoolean) {
self::$functionReplaceToLocale[] = '$1' . trim($localeBoolean) . '$2';
}
}
return self::translateFormula(
self::$functionReplaceFromExcel,
self::$functionReplaceToLocale,
$formula,
',',
self::$localeArgumentSeparator
);
}
/** @var ?array */
private static ?array $functionReplaceFromLocale;
/** @var ?array */
private static ?array $functionReplaceToExcel;
/**
* @deprecated 1.30.0 use translateFormulaToEnglish() instead
*
* @codeCoverageIgnore
*/
public function _translateFormulaToEnglish(string $formula): string
{
return $this->translateFormulaToEnglish($formula);
}
public function translateFormulaToEnglish(string $formula): string
{
if (self::$functionReplaceFromLocale === null) {
self::$functionReplaceFromLocale = [];
foreach (self::$localeFunctions as $localeFunctionName) {
self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName, '/') . '([\s]*\()/ui';
}
foreach (self::$localeBoolean as $excelBoolean) {
self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/ui';
}
}
if (self::$functionReplaceToExcel === null) {
self::$functionReplaceToExcel = [];
foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
self::$functionReplaceToExcel[] = '$1' . trim($excelFunctionName) . '$2';
}
foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
self::$functionReplaceToExcel[] = '$1' . trim($excelBoolean) . '$2';
}
}
return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator, ',');
}
public static function localeFunc(string $function): string
{
if (self::$localeLanguage !== 'en_us') {
$functionName = trim($function, '(');
if (isset(self::$localeFunctions[$functionName])) {
$brace = ($functionName != $function);
$function = self::$localeFunctions[$functionName];
if ($brace) {
$function .= '(';
}
}
}
return $function;
}
/**
* Wrap string values in quotes.
*/
public static function wrapResult(mixed $value): mixed
{
if (is_string($value)) {
// Error values cannot be "wrapped"
if (preg_match('/^' . self::CALCULATION_REGEXP_ERROR . '$/i', $value, $match)) {
// Return Excel errors "as is"
return $value;
}
// Return strings wrapped in quotes
return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE;
} elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
// Convert numeric errors to NaN error
return ExcelError::NAN();
}
return $value;
}
/**
* Remove quotes used as a wrapper to identify string values.
*/
public static function unwrapResult(mixed $value): mixed
{
if (is_string($value)) {
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
} elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
return ExcelError::NAN();
}
return $value;
}
/**
* Calculate cell value (using formula from a cell ID)
* Retained for backward compatibility.
*
* @param ?Cell $cell Cell to calculate
*/
public function calculate(?Cell $cell = null): mixed
{
try {
return $this->calculateCellValue($cell);
} catch (\Exception $e) {
throw new Exception($e->getMessage());
}
}
/**
* Calculate the value of a cell formula.
*
* @param ?Cell $cell Cell to calculate
* @param bool $resetLog Flag indicating whether the debug log should be reset or not
*/
public function calculateCellValue(?Cell $cell = null, bool $resetLog = true): mixed
{
if ($cell === null) {
return null;
}
$returnArrayAsType = self::$returnArrayAsType;
if ($resetLog) {
// Initialise the logging settings if requested
$this->formulaError = null;
$this->debugLog->clearLog();
$this->cyclicReferenceStack->clear();
$this->cyclicFormulaCounter = 1;
self::$returnArrayAsType = self::RETURN_ARRAY_AS_ARRAY;
}
// Execute the calculation for the cell formula
$this->cellStack[] = [
'sheet' => $cell->getWorksheet()->getTitle(),
'cell' => $cell->getCoordinate(),
];
$cellAddressAttempted = false;
$cellAddress = null;
try {
$result = self::unwrapResult($this->_calculateFormulaValue($cell->getValue(), $cell->getCoordinate(), $cell));
if ($this->spreadsheet === null) {
throw new Exception('null spreadsheet in calculateCellValue');
}
$cellAddressAttempted = true;
$cellAddress = array_pop($this->cellStack);
if ($cellAddress === null) {
throw new Exception('null cellAddress in calculateCellValue');
}
$testSheet = $this->spreadsheet->getSheetByName($cellAddress['sheet']);
if ($testSheet === null) {
throw new Exception('worksheet not found in calculateCellValue');
}
$testSheet->getCell($cellAddress['cell']);
} catch (\Exception $e) {
if (!$cellAddressAttempted) {
$cellAddress = array_pop($this->cellStack);
}
if ($this->spreadsheet !== null && is_array($cellAddress) && array_key_exists('sheet', $cellAddress)) {
$testSheet = $this->spreadsheet->getSheetByName($cellAddress['sheet']);
if ($testSheet !== null && array_key_exists('cell', $cellAddress)) {
$testSheet->getCell($cellAddress['cell']);
}
}
throw new Exception($e->getMessage(), $e->getCode(), $e);
}
if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
self::$returnArrayAsType = $returnArrayAsType;
$testResult = Functions::flattenArray($result);
if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) {
return ExcelError::VALUE();
}
// If there's only a single cell in the array, then we allow it
if (count($testResult) != 1) {
// If keys are numeric, then it's a matrix result rather than a cell range result, so we permit it
$r = array_keys($result);
$r = array_shift($r);
if (!is_numeric($r)) {
return ExcelError::VALUE();
}
if (is_array($result[$r])) {
$c = array_keys($result[$r]);
$c = array_shift($c);
if (!is_numeric($c)) {
return ExcelError::VALUE();
}
}
}
$result = array_shift($testResult);
}
self::$returnArrayAsType = $returnArrayAsType;
if ($result === null && $cell->getWorksheet()->getSheetView()->getShowZeros()) {
return 0;
} elseif ((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) {
return ExcelError::NAN();
}
return $result;
}
/**
* Validate and parse a formula string.
*
* @param string $formula Formula to parse
*/
public function parseFormula(string $formula): array|bool
{
// Basic validation that this is indeed a formula
// We return an empty array if not
$formula = trim($formula);
if ((!isset($formula[0])) || ($formula[0] != '=')) {
return [];
}
$formula = ltrim(substr($formula, 1));
if (!isset($formula[0])) {
return [];
}
// Parse the formula and return the token stack
return $this->internalParseFormula($formula);
}
/**
* Calculate the value of a formula.
*
* @param string $formula Formula to parse
* @param ?string $cellID Address of the cell to calculate
* @param ?Cell $cell Cell to calculate
*/
public function calculateFormula(string $formula, ?string $cellID = null, ?Cell $cell = null): mixed
{
// Initialise the logging settings
$this->formulaError = null;
$this->debugLog->clearLog();
$this->cyclicReferenceStack->clear();
$resetCache = $this->getCalculationCacheEnabled();
if ($this->spreadsheet !== null && $cellID === null && $cell === null) {
$cellID = 'A1';
$cell = $this->spreadsheet->getActiveSheet()->getCell($cellID);
} else {
// Disable calculation cacheing because it only applies to cell calculations, not straight formulae
// But don't actually flush any cache
$this->calculationCacheEnabled = false;
}
// Execute the calculation
try {
$result = self::unwrapResult($this->_calculateFormulaValue($formula, $cellID, $cell));
} catch (\Exception $e) {
throw new Exception($e->getMessage());
}
if ($this->spreadsheet === null) {
// Reset calculation cacheing to its previous state
$this->calculationCacheEnabled = $resetCache;
}
return $result;
}
public function getValueFromCache(string $cellReference, mixed &$cellValue): bool
{
$this->debugLog->writeDebugLog('Testing cache value for cell %s', $cellReference);
// Is calculation cacheing enabled?
// If so, is the required value present in calculation cache?
if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) {
$this->debugLog->writeDebugLog('Retrieving value for cell %s from cache', $cellReference);
// Return the cached result
$cellValue = $this->calculationCache[$cellReference];
return true;
}
return false;
}
public function saveValueToCache(string $cellReference, mixed $cellValue): void
{
if ($this->calculationCacheEnabled) {
$this->calculationCache[$cellReference] = $cellValue;
}
}
/**
* Parse a cell formula and calculate its value.
*
* @param string $formula The formula to parse and calculate
* @param ?string $cellID The ID (e.g. A3) of the cell that we are calculating
* @param ?Cell $cell Cell to calculate
* @param bool $ignoreQuotePrefix If set to true, evaluate the formyla even if the referenced cell is quote prefixed
*/
public function _calculateFormulaValue(string $formula, ?string $cellID = null, ?Cell $cell = null, bool $ignoreQuotePrefix = false): mixed
{
$cellValue = null;
// Quote-Prefixed cell values cannot be formulae, but are treated as strings
if ($cell !== null && $ignoreQuotePrefix === false && $cell->getStyle()->getQuotePrefix() === true) {
return self::wrapResult((string) $formula);
}
if (preg_match('/^=\s*cmd\s*\|/miu', $formula) !== 0) {
return self::wrapResult($formula);
}
// Basic validation that this is indeed a formula
// We simply return the cell value if not
$formula = trim($formula);
if ($formula[0] != '=') {
return self::wrapResult($formula);
}
$formula = ltrim(substr($formula, 1));
if (!isset($formula[0])) {
return self::wrapResult($formula);
}
$pCellParent = ($cell !== null) ? $cell->getWorksheet() : null;
$wsTitle = ($pCellParent !== null) ? $pCellParent->getTitle() : "\x00Wrk";
$wsCellReference = $wsTitle . '!' . $cellID;
if (($cellID !== null) && ($this->getValueFromCache($wsCellReference, $cellValue))) {
return $cellValue;
}
$this->debugLog->writeDebugLog('Evaluating formula for cell %s', $wsCellReference);
if (($wsTitle[0] !== "\x00") && ($this->cyclicReferenceStack->onStack($wsCellReference))) {
if ($this->cyclicFormulaCount <= 0) {
$this->cyclicFormulaCell = '';
return $this->raiseFormulaError('Cyclic Reference in Formula');
} elseif ($this->cyclicFormulaCell === $wsCellReference) {
++$this->cyclicFormulaCounter;
if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
$this->cyclicFormulaCell = '';
return $cellValue;
}
} elseif ($this->cyclicFormulaCell == '') {
if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
return $cellValue;
}
$this->cyclicFormulaCell = $wsCellReference;
}
}
$this->debugLog->writeDebugLog('Formula for cell %s is %s', $wsCellReference, $formula);
// Parse the formula onto the token stack and calculate the value
$this->cyclicReferenceStack->push($wsCellReference);
$cellValue = $this->processTokenStack($this->internalParseFormula($formula, $cell), $cellID, $cell);
$this->cyclicReferenceStack->pop();
// Save to calculation cache
if ($cellID !== null) {
$this->saveValueToCache($wsCellReference, $cellValue);
}
// Return the calculated value
return $cellValue;
}
/**
* Ensure that paired matrix operands are both matrices and of the same size.
*
* @param mixed $operand1 First matrix operand
* @param mixed $operand2 Second matrix operand
* @param int $resize Flag indicating whether the matrices should be resized to match
* and (if so), whether the smaller dimension should grow or the
* larger should shrink.
* 0 = no resize
* 1 = shrink to fit
* 2 = extend to fit
*/
private static function checkMatrixOperands(mixed &$operand1, mixed &$operand2, int $resize = 1): array
{
// Examine each of the two operands, and turn them into an array if they aren't one already
// Note that this function should only be called if one or both of the operand is already an array
if (!is_array($operand1)) {
[$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand2);
$operand1 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand1));
$resize = 0;
} elseif (!is_array($operand2)) {
[$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand1);
$operand2 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand2));
$resize = 0;
}
[$matrix1Rows, $matrix1Columns] = self::getMatrixDimensions($operand1);
[$matrix2Rows, $matrix2Columns] = self::getMatrixDimensions($operand2);
if (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) {
$resize = 1;
}
if ($resize == 2) {
// Given two matrices of (potentially) unequal size, convert the smaller in each dimension to match the larger
self::resizeMatricesExtend($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
} elseif ($resize == 1) {
// Given two matrices of (potentially) unequal size, convert the larger in each dimension to match the smaller
self::resizeMatricesShrink($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
}
[$matrix1Rows, $matrix1Columns] = self::getMatrixDimensions($operand1);
[$matrix2Rows, $matrix2Columns] = self::getMatrixDimensions($operand2);
return [$matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns];
}
/**
* Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0.
*
* @param array $matrix matrix operand
*
* @return int[] An array comprising the number of rows, and number of columns
*/
public static function getMatrixDimensions(array &$matrix): array
{
$matrixRows = count($matrix);
$matrixColumns = 0;
foreach ($matrix as $rowKey => $rowValue) {
if (!is_array($rowValue)) {
$matrix[$rowKey] = [$rowValue];
$matrixColumns = max(1, $matrixColumns);
} else {
$matrix[$rowKey] = array_values($rowValue);
$matrixColumns = max(count($rowValue), $matrixColumns);
}
}
$matrix = array_values($matrix);
return [$matrixRows, $matrixColumns];
}
/**
* Ensure that paired matrix operands are both matrices of the same size.
*
* @param array $matrix1 First matrix operand
* @param array $matrix2 Second matrix operand
* @param int $matrix1Rows Row size of first matrix operand
* @param int $matrix1Columns Column size of first matrix operand
* @param int $matrix2Rows Row size of second matrix operand
* @param int $matrix2Columns Column size of second matrix operand
*/
private static function resizeMatricesShrink(array &$matrix1, array &$matrix2, int $matrix1Rows, int $matrix1Columns, int $matrix2Rows, int $matrix2Columns): void
{
if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
if ($matrix2Rows < $matrix1Rows) {
for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) {
unset($matrix1[$i]);
}
}
if ($matrix2Columns < $matrix1Columns) {
for ($i = 0; $i < $matrix1Rows; ++$i) {
for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
unset($matrix1[$i][$j]);
}
}
}
}
if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
if ($matrix1Rows < $matrix2Rows) {
for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) {
unset($matrix2[$i]);
}
}
if ($matrix1Columns < $matrix2Columns) {
for ($i = 0; $i < $matrix2Rows; ++$i) {
for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
unset($matrix2[$i][$j]);
}
}
}
}
}
/**
* Ensure that paired matrix operands are both matrices of the same size.
*
* @param array $matrix1 First matrix operand
* @param array $matrix2 Second matrix operand
* @param int $matrix1Rows Row size of first matrix operand
* @param int $matrix1Columns Column size of first matrix operand
* @param int $matrix2Rows Row size of second matrix operand
* @param int $matrix2Columns Column size of second matrix operand
*/
private static function resizeMatricesExtend(array &$matrix1, array &$matrix2, int $matrix1Rows, int $matrix1Columns, int $matrix2Rows, int $matrix2Columns): void
{
if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
if ($matrix2Columns < $matrix1Columns) {
for ($i = 0; $i < $matrix2Rows; ++$i) {
$x = $matrix2[$i][$matrix2Columns - 1];
for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
$matrix2[$i][$j] = $x;
}
}
}
if ($matrix2Rows < $matrix1Rows) {
$x = $matrix2[$matrix2Rows - 1];
for ($i = 0; $i < $matrix1Rows; ++$i) {
$matrix2[$i] = $x;
}
}
}
if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
if ($matrix1Columns < $matrix2Columns) {
for ($i = 0; $i < $matrix1Rows; ++$i) {
$x = $matrix1[$i][$matrix1Columns - 1];
for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
$matrix1[$i][$j] = $x;
}
}
}
if ($matrix1Rows < $matrix2Rows) {
$x = $matrix1[$matrix1Rows - 1];
for ($i = 0; $i < $matrix2Rows; ++$i) {
$matrix1[$i] = $x;
}
}
}
}
/**
* Format details of an operand for display in the log (based on operand type).
*
* @param mixed $value First matrix operand
*/
private function showValue(mixed $value): mixed
{
if ($this->debugLog->getWriteDebugLog()) {
$testArray = Functions::flattenArray($value);
if (count($testArray) == 1) {
$value = array_pop($testArray);
}
if (is_array($value)) {
$returnMatrix = [];
$pad = $rpad = ', ';
foreach ($value as $row) {
if (is_array($row)) {
$returnMatrix[] = implode($pad, array_map([$this, 'showValue'], $row));
$rpad = '; ';
} else {
$returnMatrix[] = $this->showValue($row);
}
}
return '{ ' . implode($rpad, $returnMatrix) . ' }';
} 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'];
} elseif ($value === null) {
return self::$localeBoolean['NULL'];
}
}
return Functions::flattenSingleValue($value);
}
/**
* Format type and details of an operand for display in the log (based on operand type).
*
* @param mixed $value First matrix operand
*/
private function showTypeDetails(mixed $value): ?string
{
if ($this->debugLog->getWriteDebugLog()) {
$testArray = Functions::flattenArray($value);
if (count($testArray) == 1) {
$value = array_pop($testArray);
}
if ($value === null) {
return 'a NULL value';
} elseif (is_float($value)) {
$typeString = 'a floating point number';
} elseif (is_int($value)) {
$typeString = 'an integer number';
} elseif (is_bool($value)) {
$typeString = 'a boolean';
} elseif (is_array($value)) {
$typeString = 'a matrix';
} else {
if ($value == '') {
return 'an empty string';
} elseif ($value[0] == '#') {
return 'a ' . $value . ' error';
}
$typeString = 'a string';
}
return $typeString . ' with a value of ' . $this->showValue($value);
}
return null;
}
/**
* @return false|string False indicates an error
*/
private function convertMatrixReferences(string $formula): false|string
{
static $matrixReplaceFrom = [self::FORMULA_OPEN_MATRIX_BRACE, ';', self::FORMULA_CLOSE_MATRIX_BRACE];
static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))'];
// Convert any Excel matrix references to the MKMATRIX() function
if (str_contains($formula, self::FORMULA_OPEN_MATRIX_BRACE)) {
// If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
if (str_contains($formula, self::FORMULA_STRING_QUOTE)) {
// 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(self::FORMULA_STRING_QUOTE, $formula);
// Open and Closed counts used for trapping mismatched braces in the formula
$openCount = $closeCount = 0;
$notWithinQuotes = false;
foreach ($temp as &$value) {
// Only count/replace in alternating array entries
$notWithinQuotes = $notWithinQuotes === false;
if ($notWithinQuotes === true) {
$openCount += substr_count($value, self::FORMULA_OPEN_MATRIX_BRACE);
$closeCount += substr_count($value, self::FORMULA_CLOSE_MATRIX_BRACE);
$value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
}
}
unset($value);
// Then rebuild the formula string
$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, self::FORMULA_OPEN_MATRIX_BRACE);
$closeCount = substr_count($formula, self::FORMULA_CLOSE_MATRIX_BRACE);
$formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
}
// Trap for mismatched braces and trigger an appropriate error
if ($openCount < $closeCount) {
if ($openCount > 0) {
return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '}'");
}
return $this->raiseFormulaError("Formula Error: Unexpected '}' encountered");
} elseif ($openCount > $closeCount) {
if ($closeCount > 0) {
return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '{'");
}
return $this->raiseFormulaError("Formula Error: Unexpected '{' encountered");
}
}
return $formula;
}
/**
* Binary Operators.
* These operators always work on two values.
* Array key is the operator, the value indicates whether this is a left or right associative operator.
*/
private static array $operatorAssociativity = [
'^' => 0, // Exponentiation
'*' => 0, '/' => 0, // Multiplication and Division
'+' => 0, '-' => 0, // Addition and Subtraction
'&' => 0, // Concatenation
'∪' => 0, '∩' => 0, ':' => 0, // Union, Intersect and Range
'>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison
];
/**
* Comparison (Boolean) Operators.
* These operators work on two values, but always return a boolean result.
*/
private static array $comparisonOperators = ['>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true];
/**
* Operator Precedence.
* This list includes all valid operators, whether binary (including boolean) or unary (such as %).
* Array key is the operator, the value is its precedence.
*/
private static array $operatorPrecedence = [
':' => 9, // Range
'∩' => 8, // Intersect
'∪' => 7, // Union
'~' => 6, // Negation
'%' => 5, // Percentage
'^' => 4, // Exponentiation
'*' => 3, '/' => 3, // Multiplication and Division
'+' => 2, '-' => 2, // Addition and Subtraction
'&' => 1, // Concatenation
'>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison
];
// Convert infix to postfix notation
/**
* @return array<int, mixed>|false
*/
private function internalParseFormula(string $formula, ?Cell $cell = null): bool|array
{
if (($formula = $this->convertMatrixReferences(trim($formula))) === false) {
return false;
}
// If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet),
// so we store the parent worksheet so that we can re-attach it when necessary
$pCellParent = ($cell !== null) ? $cell->getWorksheet() : null;
$regexpMatchString = '/^((?<string>' . self::CALCULATION_REGEXP_STRING
. ')|(?<function>' . self::CALCULATION_REGEXP_FUNCTION
. ')|(?<cellRef>' . self::CALCULATION_REGEXP_CELLREF
. ')|(?<colRange>' . self::CALCULATION_REGEXP_COLUMN_RANGE
. ')|(?<rowRange>' . self::CALCULATION_REGEXP_ROW_RANGE
. ')|(?<number>' . self::CALCULATION_REGEXP_NUMBER
. ')|(?<openBrace>' . self::CALCULATION_REGEXP_OPENBRACE
. ')|(?<structuredReference>' . self::CALCULATION_REGEXP_STRUCTURED_REFERENCE
. ')|(?<definedName>' . self::CALCULATION_REGEXP_DEFINEDNAME
. ')|(?<error>' . self::CALCULATION_REGEXP_ERROR
. '))/sui';
// Start with initialisation
$index = 0;
$stack = new Stack($this->branchPruner);
$output = [];
$expectingOperator = false; // We use this test in syntax-checking the expression to determine when a
// - is a negation or + is a positive operator rather than an operation
$expectingOperand = false; // We use this test in syntax-checking the expression to determine whether an operand
// should be null in a function call
// The guts of the lexical parser
// Loop through the formula extracting each operator and operand in turn
while (true) {
// Branch pruning: we adapt the output item to the context (it will
// be used to limit its computation)
$this->branchPruner->initialiseForLoop();
$opCharacter = $formula[$index]; // Get the first character of the value at the current index position
// Check for two-character operators (e.g. >=, <=, <>)
if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula[$index + 1]]))) {
$opCharacter .= $formula[++$index];
}
// Find out if we're currently at the beginning of a number, variable, cell/row/column reference,
// function, defined name, structured reference, parenthesis, error or operand
$isOperandOrFunction = (bool) preg_match($regexpMatchString, substr($formula, $index), $match);
$expectingOperatorCopy = $expectingOperator;
if ($opCharacter === '-' && !$expectingOperator) { // Is it a negation instead of a minus?
// Put a negation on the stack
$stack->push('Unary Operator', '~');
++$index; // and drop the negation symbol
} elseif ($opCharacter === '%' && $expectingOperator) {
// Put a percentage on the stack
$stack->push('Unary Operator', '%');
++$index;
} elseif ($opCharacter === '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded?
++$index; // Drop the redundant plus symbol
} elseif ((($opCharacter === '~') || ($opCharacter === '∩') || ($opCharacter === '∪')) && (!$isOperandOrFunction)) {
// We have to explicitly deny a tilde, union or intersect because they are legal
return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression
} elseif ((isset(self::CALCULATION_OPERATORS[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack?
while (
$stack->count() > 0
&& ($o2 = $stack->last())
&& isset(self::CALCULATION_OPERATORS[$o2['value']])
&& @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])
) {
$output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
}
// Finally put our current operator onto the stack
$stack->push('Binary Operator', $opCharacter);
++$index;
$expectingOperator = false;
} elseif ($opCharacter === ')' && $expectingOperator) { // Are we expecting to close a parenthesis?
$expectingOperand = false;
while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last (
$output[] = $o2;
}
$d = $stack->last(2);
// Branch pruning we decrease the depth whether is it a function
// call or a parenthesis
$this->branchPruner->decrementDepth();
if (is_array($d) && preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'], $matches)) {
// Did this parenthesis just close a function?
try {
$this->branchPruner->closingBrace($d['value']);
} catch (Exception $e) {
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
}
$functionName = $matches[1]; // Get the function name
$d = $stack->pop();
$argumentCount = $d['value'] ?? 0; // See how many arguments there were (argument count is the next value stored on the stack)
$output[] = $d; // Dump the argument count on the output
$output[] = $stack->pop(); // Pop the function and push onto the output
if (isset(self::$controlFunctions[$functionName])) {
$expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount'];
} elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) {
$expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount'];
} else { // did we somehow push a non-function on the stack? this should never happen
return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack');
}
// Check the argument count
$argumentCountError = false;
$expectedArgumentCountString = null;
if (is_numeric($expectedArgumentCount)) {
if ($expectedArgumentCount < 0) {
if ($argumentCount > abs($expectedArgumentCount)) {
$argumentCountError = true;
$expectedArgumentCountString = 'no more than ' . abs($expectedArgumentCount);
}
} else {
if ($argumentCount != $expectedArgumentCount) {
$argumentCountError = true;
$expectedArgumentCountString = $expectedArgumentCount;
}
}
} elseif ($expectedArgumentCount != '*') {
preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch);
switch ($argMatch[2] ?? '') {
case '+':
if ($argumentCount < $argMatch[1]) {
$argumentCountError = true;
$expectedArgumentCountString = $argMatch[1] . ' or more ';
}
break;
case '-':
if (($argumentCount < $argMatch[1]) || ($argumentCount > $argMatch[3])) {
$argumentCountError = true;
$expectedArgumentCountString = 'between ' . $argMatch[1] . ' and ' . $argMatch[3];
}
break;
case ',':
if (($argumentCount != $argMatch[1]) && ($argumentCount != $argMatch[3])) {
$argumentCountError = true;
$expectedArgumentCountString = 'either ' . $argMatch[1] . ' or ' . $argMatch[3];
}
break;
}
}
if ($argumentCountError) {
return $this->raiseFormulaError("Formula Error: Wrong number of arguments for $functionName() function: $argumentCount given, " . $expectedArgumentCountString . ' expected');
}
}
++$index;
} elseif ($opCharacter === ',') { // Is this the separator for function arguments?
try {
$this->branchPruner->argumentSeparator();
} catch (Exception $e) {
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
}
while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last (
$output[] = $o2; // pop the argument expression stuff and push onto the output
}
// If we've a comma when we're expecting an operand, then what we actually have is a null operand;
// so push a null onto the stack
if (($expectingOperand) || (!$expectingOperator)) {
$output[] = $stack->getStackItem('Empty Argument', null, 'NULL');
}
// make sure there was a function
$d = $stack->last(2);
if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'] ?? '', $matches)) {
// Can we inject a dummy function at this point so that the braces at least have some context
// because at least the braces are paired up (at this stage in the formula)
// MS Excel allows this if the content is cell references; but doesn't allow actual values,
// but at this point, we can't differentiate (so allow both)
return $this->raiseFormulaError('Formula Error: Unexpected ,');
}
/** @var array $d */
$d = $stack->pop();
++$d['value']; // increment the argument count
$stack->pushStackItem($d);
$stack->push('Brace', '('); // put the ( back on, we'll need to pop back to it again
$expectingOperator = false;
$expectingOperand = true;
++$index;
} elseif ($opCharacter === '(' && !$expectingOperator) {
// Branch pruning: we go deeper
$this->branchPruner->incrementDepth();
$stack->push('Brace', '(', null);
++$index;
} elseif ($isOperandOrFunction && !$expectingOperatorCopy) {
// do we now have a function/variable/number?
$expectingOperator = true;
$expectingOperand = false;
$val = $match[1];
$length = strlen($val);
if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $val, $matches)) {
$val = (string) preg_replace('/\s/u', '', $val);
if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) { // it's a function
$valToUpper = strtoupper($val);
} else {
$valToUpper = 'NAME.ERROR(';
}
// here $matches[1] will contain values like "IF"
// and $val "IF("
$this->branchPruner->functionCall($valToUpper);
$stack->push('Function', $valToUpper);
// tests if the function is closed right after opening
$ax = preg_match('/^\s*\)/u', substr($formula, $index + $length));
if ($ax) {
$stack->push('Operand Count for Function ' . $valToUpper . ')', 0);
$expectingOperator = true;
} else {
$stack->push('Operand Count for Function ' . $valToUpper . ')', 1);
$expectingOperator = false;
}
$stack->push('Brace', '(');
} elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $val, $matches)) {
// Watch for this case-change when modifying to allow cell references in different worksheets...
// Should only be applied to the actual cell column, not the worksheet name
// If the last entry on the stack was a : operator, then we have a cell range reference
$testPrevOp = $stack->last(1);
if ($testPrevOp !== null && $testPrevOp['value'] === ':') {
// If we have a worksheet reference, then we're playing with a 3D reference
if ($matches[2] === '') {
// Otherwise, we 'inherit' the worksheet reference from the start cell reference
// The start of the cell range reference should be the last entry in $output
$rangeStartCellRef = $output[count($output) - 1]['value'] ?? '';
if ($rangeStartCellRef === ':') {
// Do we have chained range operators?
$rangeStartCellRef = $output[count($output) - 2]['value'] ?? '';
}
preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches);
if (array_key_exists(2, $rangeStartMatches)) {
if ($rangeStartMatches[2] > '') {
$val = $rangeStartMatches[2] . '!' . $val;
}
} else {
$val = ExcelError::REF();
}
} else {
$rangeStartCellRef = $output[count($output) - 1]['value'] ?? '';
if ($rangeStartCellRef === ':') {
// Do we have chained range operators?
$rangeStartCellRef = $output[count($output) - 2]['value'] ?? '';
}
preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches);
if ($rangeStartMatches[2] !== $matches[2]) {
return $this->raiseFormulaError('3D Range references are not yet supported');
}
}
} elseif (!str_contains($val, '!') && $pCellParent !== null) {
$worksheet = $pCellParent->getTitle();
$val = "'{$worksheet}'!{$val}";
}
// unescape any apostrophes or double quotes in worksheet name
$val = str_replace(["''", '""'], ["'", '"'], $val);
$outputItem = $stack->getStackItem('Cell Reference', $val, $val);
$output[] = $outputItem;
} elseif (preg_match('/^' . self::CALCULATION_REGEXP_STRUCTURED_REFERENCE . '$/miu', $val, $matches)) {
try {
$structuredReference = Operands\StructuredReference::fromParser($formula, $index, $matches);
} catch (Exception $e) {
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
}
$val = $structuredReference->value();
$length = strlen($val);
$outputItem = $stack->getStackItem(Operands\StructuredReference::NAME, $structuredReference, null);
$output[] = $outputItem;
$expectingOperator = true;
} else {
// it's a variable, constant, string, number or boolean
$localeConstant = false;
$stackItemType = 'Value';
$stackItemReference = null;
// If the last entry on the stack was a : operator, then we may have a row or column range reference
$testPrevOp = $stack->last(1);
if ($testPrevOp !== null && $testPrevOp['value'] === ':') {
$stackItemType = 'Cell Reference';
if (
!is_numeric($val)
&& ((ctype_alpha($val) === false || strlen($val) > 3))
&& (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $val) !== false)
&& ($this->spreadsheet === null || $this->spreadsheet->getNamedRange($val) !== null)
) {
$namedRange = ($this->spreadsheet === null) ? null : $this->spreadsheet->getNamedRange($val);
if ($namedRange !== null) {
$stackItemType = 'Defined Name';
$address = str_replace('$', '', $namedRange->getValue());
$stackItemReference = $val;
if (str_contains($address, ':')) {
// We'll need to manipulate the stack for an actual named range rather than a named cell
$fromTo = explode(':', $address);
$to = array_pop($fromTo);
foreach ($fromTo as $from) {
$output[] = $stack->getStackItem($stackItemType, $from, $stackItemReference);
$output[] = $stack->getStackItem('Binary Operator', ':');
}
$address = $to;
}
$val = $address;
}
} elseif ($val === ExcelError::REF()) {
$stackItemReference = $val;
} else {
/** @var non-empty-string $startRowColRef */
$startRowColRef = $output[count($output) - 1]['value'] ?? '';
[$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true);
$rangeSheetRef = $rangeWS1;
if ($rangeWS1 !== '') {
$rangeWS1 .= '!';
}
$rangeSheetRef = trim($rangeSheetRef, "'");
[$rangeWS2, $val] = Worksheet::extractSheetTitle($val, true);
if ($rangeWS2 !== '') {
$rangeWS2 .= '!';
} else {
$rangeWS2 = $rangeWS1;
}
$refSheet = $pCellParent;
if ($pCellParent !== null && $rangeSheetRef !== '' && $rangeSheetRef !== $pCellParent->getTitle()) {
$refSheet = $pCellParent->getParentOrThrow()->getSheetByName($rangeSheetRef);
}
if (ctype_digit($val) && $val <= 1048576) {
// Row range
$stackItemType = 'Row Reference';
/** @var int $valx */
$valx = $val;
$endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataColumn($valx) : AddressRange::MAX_COLUMN; // Max 16,384 columns for Excel2007
$val = "{$rangeWS2}{$endRowColRef}{$val}";
} elseif (ctype_alpha($val) && strlen($val ?? '') <= 3) {
// Column range
$stackItemType = 'Column Reference';
$endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataRow($val) : AddressRange::MAX_ROW; // Max 1,048,576 rows for Excel2007
$val = "{$rangeWS2}{$val}{$endRowColRef}";
}
$stackItemReference = $val;
}
} elseif ($opCharacter === self::FORMULA_STRING_QUOTE) {
// UnEscape any quotes within the string
$val = self::wrapResult(str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($val)));
} elseif (isset(self::$excelConstants[trim(strtoupper($val))])) {
$stackItemType = 'Constant';
$excelConstant = trim(strtoupper($val));
$val = self::$excelConstants[$excelConstant];
$stackItemReference = $excelConstant;
} elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) {
$stackItemType = 'Constant';
$val = self::$excelConstants[$localeConstant];
$stackItemReference = $localeConstant;
} elseif (
preg_match('/^' . self::CALCULATION_REGEXP_ROW_RANGE . '/miu', substr($formula, $index), $rowRangeReference)
) {
$val = $rowRangeReference[1];
$length = strlen($rowRangeReference[1]);
$stackItemType = 'Row Reference';
// unescape any apostrophes or double quotes in worksheet name
$val = str_replace(["''", '""'], ["'", '"'], $val);
$column = 'A';
if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) {
$column = $pCellParent->getHighestDataColumn($val);
}
$val = "{$rowRangeReference[2]}{$column}{$rowRangeReference[7]}";
$stackItemReference = $val;
} elseif (
preg_match('/^' . self::CALCULATION_REGEXP_COLUMN_RANGE . '/miu', substr($formula, $index), $columnRangeReference)
) {
$val = $columnRangeReference[1];
$length = strlen($val);
$stackItemType = 'Column Reference';
// unescape any apostrophes or double quotes in worksheet name
$val = str_replace(["''", '""'], ["'", '"'], $val);
$row = '1';
if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) {
$row = $pCellParent->getHighestDataRow($val);
}
$val = "{$val}{$row}";
$stackItemReference = $val;
} elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', $val, $match)) {
$stackItemType = 'Defined Name';
$stackItemReference = $val;
} elseif (is_numeric($val)) {
if ((str_contains((string) $val, '.')) || (stripos((string) $val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) {
$val = (float) $val;
} else {
$val = (int) $val;
}
}
$details = $stack->getStackItem($stackItemType, $val, $stackItemReference);
if ($localeConstant) {
$details['localeValue'] = $localeConstant;
}
$output[] = $details;
}
$index += $length;
} elseif ($opCharacter === '$') { // absolute row or column range
++$index;
} elseif ($opCharacter === ')') { // miscellaneous error checking
if ($expectingOperand) {
$output[] = $stack->getStackItem('Empty Argument', null, 'NULL');
$expectingOperand = false;
$expectingOperator = true;
} else {
return $this->raiseFormulaError("Formula Error: Unexpected ')'");
}
} elseif (isset(self::CALCULATION_OPERATORS[$opCharacter]) && !$expectingOperator) {
return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'");
} else { // I don't even want to know what you did to get here
return $this->raiseFormulaError('Formula Error: An unexpected error occurred');
}
// Test for end of formula string
if ($index == strlen($formula)) {
// Did we end with an operator?.
// Only valid for the % unary operator
if ((isset(self::CALCULATION_OPERATORS[$opCharacter])) && ($opCharacter != '%')) {
return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands");
}
break;
}
// Ignore white space
while (($formula[$index] === "\n") || ($formula[$index] === "\r")) {
++$index;
}
if ($formula[$index] === ' ') {
while ($formula[$index] === ' ') {
++$index;
}
// If we're expecting an operator, but only have a space between the previous and next operands (and both are
// Cell References, Defined Names or Structured References) then we have an INTERSECTION operator
$countOutputMinus1 = count($output) - 1;
if (
($expectingOperator)
&& array_key_exists($countOutputMinus1, $output)
&& is_array($output[$countOutputMinus1])
&& array_key_exists('type', $output[$countOutputMinus1])
&& (
(preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/miu', substr($formula, $index), $match))
&& ($output[$countOutputMinus1]['type'] === 'Cell Reference')
|| (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', substr($formula, $index), $match))
&& ($output[$countOutputMinus1]['type'] === 'Defined Name' || $output[$countOutputMinus1]['type'] === 'Value')
|| (preg_match('/^' . self::CALCULATION_REGEXP_STRUCTURED_REFERENCE . '.*/miu', substr($formula, $index), $match))
&& ($output[$countOutputMinus1]['type'] === Operands\StructuredReference::NAME || $output[$countOutputMinus1]['type'] === 'Value')
)
) {
while (
$stack->count() > 0
&& ($o2 = $stack->last())
&& isset(self::CALCULATION_OPERATORS[$o2['value']])
&& @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])
) {
$output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
}
$stack->push('Binary Operator', '∩'); // Put an Intersect Operator on the stack
$expectingOperator = false;
}
}
}
while (($op = $stack->pop()) !== null) {
// pop everything off the stack and push onto output
if ((is_array($op) && $op['value'] == '(')) {
return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced
}
$output[] = $op;
}
return $output;
}
private static function dataTestReference(array &$operandData): mixed
{
$operand = $operandData['value'];
if (($operandData['reference'] === null) && (is_array($operand))) {
$rKeys = array_keys($operand);
$rowKey = array_shift($rKeys);
if (is_array($operand[$rowKey]) === false) {
$operandData['value'] = $operand[$rowKey];
return $operand[$rowKey];
}
$cKeys = array_keys(array_keys($operand[$rowKey]));
$colKey = array_shift($cKeys);
if (ctype_upper("$colKey")) {
$operandData['reference'] = $colKey . $rowKey;
}
}
return $operand;
}
/**
* @return array<int, mixed>|false
*/
private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell $cell = null)
{
if ($tokens === false) {
return false;
}
// If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection),
// so we store the parent cell collection so that we can re-attach it when necessary
$pCellWorksheet = ($cell !== null) ? $cell->getWorksheet() : null;
$pCellParent = ($cell !== null) ? $cell->getParent() : null;
$stack = new Stack($this->branchPruner);
// Stores branches that have been pruned
$fakedForBranchPruning = [];
// help us to know when pruning ['branchTestId' => true/false]
$branchStore = [];
// Loop through each token in turn
foreach ($tokens as $tokenData) {
$token = $tokenData['value'];
// Branch pruning: skip useless resolutions
$storeKey = $tokenData['storeKey'] ?? null;
if ($this->branchPruningEnabled && isset($tokenData['onlyIf'])) {
$onlyIfStoreKey = $tokenData['onlyIf'];
$storeValue = $branchStore[$onlyIfStoreKey] ?? null;
$storeValueAsBool = ($storeValue === null)
? true : (bool) Functions::flattenSingleValue($storeValue);
if (is_array($storeValue)) {
$wrappedItem = end($storeValue);
$storeValue = is_array($wrappedItem) ? end($wrappedItem) : $wrappedItem;
}
if (
(isset($storeValue) || $tokenData['reference'] === 'NULL')
&& (!$storeValueAsBool || Information\ErrorValue::isError($storeValue) || ($storeValue === 'Pruned branch'))
) {
// If branching value is not true, we don't need to compute
if (!isset($fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey])) {
$stack->push('Value', 'Pruned branch (only if ' . $onlyIfStoreKey . ') ' . $token);
$fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey] = true;
}
if (isset($storeKey)) {
// We are processing an if condition
// We cascade the pruning to the depending branches
$branchStore[$storeKey] = 'Pruned branch';
$fakedForBranchPruning['onlyIfNot-' . $storeKey] = true;
$fakedForBranchPruning['onlyIf-' . $storeKey] = true;
}
continue;
}
}
if ($this->branchPruningEnabled && isset($tokenData['onlyIfNot'])) {
$onlyIfNotStoreKey = $tokenData['onlyIfNot'];
$storeValue = $branchStore[$onlyIfNotStoreKey] ?? null;
$storeValueAsBool = ($storeValue === null)
? true : (bool) Functions::flattenSingleValue($storeValue);
if (is_array($storeValue)) {
$wrappedItem = end($storeValue);
$storeValue = is_array($wrappedItem) ? end($wrappedItem) : $wrappedItem;
}
if (
(isset($storeValue) || $tokenData['reference'] === 'NULL')
&& ($storeValueAsBool || Information\ErrorValue::isError($storeValue) || ($storeValue === 'Pruned branch'))
) {
// If branching value is true, we don't need to compute
if (!isset($fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey])) {
$stack->push('Value', 'Pruned branch (only if not ' . $onlyIfNotStoreKey . ') ' . $token);
$fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey] = true;
}
if (isset($storeKey)) {
// We are processing an if condition
// We cascade the pruning to the depending branches
$branchStore[$storeKey] = 'Pruned branch';
$fakedForBranchPruning['onlyIfNot-' . $storeKey] = true;
$fakedForBranchPruning['onlyIf-' . $storeKey] = true;
}
continue;
}
}
if ($token instanceof Operands\StructuredReference) {
if ($cell === null) {
return $this->raiseFormulaError('Structured References must exist in a Cell context');
}
try {
$cellRange = $token->parse($cell);
if (str_contains($cellRange, ':')) {
$this->debugLog->writeDebugLog('Evaluating Structured Reference %s as Cell Range %s', $token->value(), $cellRange);
$rangeValue = self::getInstance($cell->getWorksheet()->getParent())->_calculateFormulaValue("={$cellRange}", $cellRange, $cell);
$stack->push('Value', $rangeValue);
$this->debugLog->writeDebugLog('Evaluated Structured Reference %s as value %s', $token->value(), $this->showValue($rangeValue));
} else {
$this->debugLog->writeDebugLog('Evaluating Structured Reference %s as Cell %s', $token->value(), $cellRange);
$cellValue = $cell->getWorksheet()->getCell($cellRange)->getCalculatedValue(false);
$stack->push('Cell Reference', $cellValue, $cellRange);
$this->debugLog->writeDebugLog('Evaluated Structured Reference %s as value %s', $token->value(), $this->showValue($cellValue));
}
} catch (Exception $e) {
if ($e->getCode() === Exception::CALCULATION_ENGINE_PUSH_TO_STACK) {
$stack->push('Error', ExcelError::REF(), null);
$this->debugLog->writeDebugLog('Evaluated Structured Reference %s as error value %s', $token->value(), ExcelError::REF());
} else {
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
}
}
} elseif (!is_numeric($token) && !is_object($token) && isset(self::BINARY_OPERATORS[$token])) {
// if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
// We must have two operands, error if we don't
$operand2Data = $stack->pop();
if ($operand2Data === null) {
return $this->raiseFormulaError('Internal error - Operand value missing from stack');
}
$operand1Data = $stack->pop();
if ($operand1Data === null) {
return $this->raiseFormulaError('Internal error - Operand value missing from stack');
}
$operand1 = self::dataTestReference($operand1Data);
$operand2 = self::dataTestReference($operand2Data);
// Log what we're doing
if ($token == ':') {
$this->debugLog->writeDebugLog('Evaluating Range %s %s %s', $this->showValue($operand1Data['reference']), $token, $this->showValue($operand2Data['reference']));
} else {
$this->debugLog->writeDebugLog('Evaluating %s %s %s', $this->showValue($operand1), $token, $this->showValue($operand2));
}
// Process the operation in the appropriate manner
switch ($token) {
// Comparison (Boolean) Operators
case '>': // Greater than
case '<': // Less than
case '>=': // Greater than or Equal to
case '<=': // Less than or Equal to
case '=': // Equality
case '<>': // Inequality
$result = $this->executeBinaryComparisonOperation($operand1, $operand2, (string) $token, $stack);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
break;
// Binary Operators
case ':': // Range
if ($operand1Data['type'] === 'Defined Name') {
if (preg_match('/$' . self::CALCULATION_REGEXP_DEFINEDNAME . '^/mui', $operand1Data['reference']) !== false && $this->spreadsheet !== null) {
$definedName = $this->spreadsheet->getNamedRange($operand1Data['reference']);
if ($definedName !== null) {
$operand1Data['reference'] = $operand1Data['value'] = str_replace('$', '', $definedName->getValue());
}
}
}
if (str_contains($operand1Data['reference'] ?? '', '!')) {
[$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true);
} else {
$sheet1 = ($pCellWorksheet !== null) ? $pCellWorksheet->getTitle() : '';
}
$sheet1 ??= '';
[$sheet2, $operand2Data['reference']] = Worksheet::extractSheetTitle($operand2Data['reference'], true);
if (empty($sheet2)) {
$sheet2 = $sheet1;
}
if (trim($sheet1, "'") === trim($sheet2, "'")) {
if ($operand1Data['reference'] === null && $cell !== null) {
if (is_array($operand1Data['value'])) {
$operand1Data['reference'] = $cell->getCoordinate();
} elseif ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) {
$operand1Data['reference'] = $cell->getColumn() . $operand1Data['value'];
} elseif (trim($operand1Data['value']) == '') {
$operand1Data['reference'] = $cell->getCoordinate();
} else {
$operand1Data['reference'] = $operand1Data['value'] . $cell->getRow();
}
}
if ($operand2Data['reference'] === null && $cell !== null) {
if (is_array($operand2Data['value'])) {
$operand2Data['reference'] = $cell->getCoordinate();
} elseif ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) {
$operand2Data['reference'] = $cell->getColumn() . $operand2Data['value'];
} elseif (trim($operand2Data['value']) == '') {
$operand2Data['reference'] = $cell->getCoordinate();
} else {
$operand2Data['reference'] = $operand2Data['value'] . $cell->getRow();
}
}
$oData = array_merge(explode(':', $operand1Data['reference'] ?? ''), explode(':', $operand2Data['reference'] ?? ''));
$oCol = $oRow = [];
$breakNeeded = false;
foreach ($oData as $oDatum) {
try {
$oCR = Coordinate::coordinateFromString($oDatum);
$oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1;
$oRow[] = $oCR[1];
} catch (\Exception) {
$stack->push('Error', ExcelError::REF(), null);
$breakNeeded = true;
break;
}
}
if ($breakNeeded) {
break;
}
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
if ($pCellParent !== null && $this->spreadsheet !== null) {
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
} else {
return $this->raiseFormulaError('Unable to access Cell Reference');
}
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellValue));
$stack->push('Cell Reference', $cellValue, $cellRef);
} else {
$this->debugLog->writeDebugLog('Evaluation Result is a #REF! Error');
$stack->push('Error', ExcelError::REF(), null);
}
break;
case '+': // Addition
case '-': // Subtraction
case '*': // Multiplication
case '/': // Division
case '^': // Exponential
$result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, $stack);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
break;
case '&': // Concatenation
// If either of the operands is a matrix, we need to treat them both as matrices
// (converting the other operand to a matrix if need be); then perform the required
// matrix operation
$operand1 = self::boolToString($operand1);
$operand2 = self::boolToString($operand2);
if (is_array($operand1) || is_array($operand2)) {
if (is_string($operand1)) {
$operand1 = self::unwrapResult($operand1);
}
if (is_string($operand2)) {
$operand2 = self::unwrapResult($operand2);
}
// Ensure that both operands are arrays/matrices
[$rows, $columns] = self::checkMatrixOperands($operand1, $operand2, 2);
for ($row = 0; $row < $rows; ++$row) {
for ($column = 0; $column < $columns; ++$column) {
$operand1[$row][$column]
= Shared\StringHelper::substring(
self::boolToString($operand1[$row][$column])
. self::boolToString($operand2[$row][$column]),
0,
DataType::MAX_STRING_LENGTH
);
}
}
$result = $operand1;
} else {
// In theory, we should truncate here.
// But I can't figure out a formula
// using the concatenation operator
// with literals that fits in 32K,
// so I don't think we can overflow here.
$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 %s', $this->showTypeDetails($result));
$stack->push('Value', $result);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
break;
case '∩': // Intersect
$rowIntersect = array_intersect_key($operand1, $operand2);
$cellIntersect = $oCol = $oRow = [];
foreach (array_keys($rowIntersect) as $row) {
$oRow[] = $row;
foreach ($rowIntersect[$row] as $col => $data) {
$oCol[] = Coordinate::columnIndexFromString($col) - 1;
$cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]);
}
}
if (count(Functions::flattenArray($cellIntersect)) === 0) {
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellIntersect));
$stack->push('Error', ExcelError::null(), null);
} else {
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':'
. Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellIntersect));
$stack->push('Value', $cellIntersect, $cellRef);
}
break;
}
} elseif (($token === '~') || ($token === '%')) {
// if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
if (($arg = $stack->pop()) === null) {
return $this->raiseFormulaError('Internal error - Operand value missing from stack');
}
$arg = $arg['value'];
if ($token === '~') {
$this->debugLog->writeDebugLog('Evaluating Negation of %s', $this->showValue($arg));
$multiplier = -1;
} else {
$this->debugLog->writeDebugLog('Evaluating Percentile of %s', $this->showValue($arg));
$multiplier = 0.01;
}
if (is_array($arg)) {
$operand2 = $multiplier;
$result = $arg;
[$rows, $columns] = self::checkMatrixOperands($result, $operand2, 0);
for ($row = 0; $row < $rows; ++$row) {
for ($column = 0; $column < $columns; ++$column) {
if (self::isNumericOrBool($result[$row][$column])) {
$result[$row][$column] *= $multiplier;
} else {
$result[$row][$column] = self::makeError($result[$row][$column]);
}
}
}
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result));
$stack->push('Value', $result);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
} else {
$this->executeNumericBinaryOperation($multiplier, $arg, '*', $stack);
}
} elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token ?? '', $matches)) {
$cellRef = null;
if (isset($matches[8])) {
if ($cell === null) {
// We can't access the range, so return a REF error
$cellValue = ExcelError::REF();
} else {
$cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10];
if ($matches[2] > '') {
$matches[2] = trim($matches[2], "\"'");
if ((str_contains($matches[2], '[')) || (str_contains($matches[2], ']'))) {
// It's a Reference to an external spreadsheet (not currently supported)
return $this->raiseFormulaError('Unable to access External Workbook');
}
$matches[2] = trim($matches[2], "\"'");
$this->debugLog->writeDebugLog('Evaluating Cell Range %s in worksheet %s', $cellRef, $matches[2]);
if ($pCellParent !== null && $this->spreadsheet !== null) {
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
} else {
return $this->raiseFormulaError('Unable to access Cell Reference');
}
$this->debugLog->writeDebugLog('Evaluation Result for cells %s in worksheet %s is %s', $cellRef, $matches[2], $this->showTypeDetails($cellValue));
} else {
$this->debugLog->writeDebugLog('Evaluating Cell Range %s in current worksheet', $cellRef);
if ($pCellParent !== null) {
$cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
} else {
return $this->raiseFormulaError('Unable to access Cell Reference');
}
$this->debugLog->writeDebugLog('Evaluation Result for cells %s is %s', $cellRef, $this->showTypeDetails($cellValue));
}
}
} else {
if ($cell === null) {
// We can't access the cell, so return a REF error
$cellValue = ExcelError::REF();
} else {
$cellRef = $matches[6] . $matches[7];
if ($matches[2] > '') {
$matches[2] = trim($matches[2], "\"'");
if ((str_contains($matches[2], '[')) || (str_contains($matches[2], ']'))) {
// It's a Reference to an external spreadsheet (not currently supported)
return $this->raiseFormulaError('Unable to access External Workbook');
}
$this->debugLog->writeDebugLog('Evaluating Cell %s in worksheet %s', $cellRef, $matches[2]);
if ($pCellParent !== null && $this->spreadsheet !== null) {
$cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
if ($cellSheet && $cellSheet->cellExists($cellRef)) {
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
$cell->attach($pCellParent);
} else {
$cellRef = ($cellSheet !== null) ? "'{$matches[2]}'!{$cellRef}" : $cellRef;
$cellValue = ($cellSheet !== null) ? null : ExcelError::REF();
}
} else {
return $this->raiseFormulaError('Unable to access Cell Reference');
}
$this->debugLog->writeDebugLog('Evaluation Result for cell %s in worksheet %s is %s', $cellRef, $matches[2], $this->showTypeDetails($cellValue));
} else {
$this->debugLog->writeDebugLog('Evaluating Cell %s in current worksheet', $cellRef);
if ($pCellParent !== null && $pCellParent->has($cellRef)) {
$cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
$cell->attach($pCellParent);
} else {
$cellValue = null;
}
$this->debugLog->writeDebugLog('Evaluation Result for cell %s is %s', $cellRef, $this->showTypeDetails($cellValue));
}
}
}
$stack->push('Cell Value', $cellValue, $cellRef);
if (isset($storeKey)) {
$branchStore[$storeKey] = $cellValue;
}
} elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $token ?? '', $matches)) {
// if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
if ($cell !== null && $pCellParent !== null) {
$cell->attach($pCellParent);
}
$functionName = $matches[1];
$argCount = $stack->pop();
$argCount = $argCount['value'];
if ($functionName !== 'MKMATRIX') {
$this->debugLog->writeDebugLog('Evaluating Function %s() with %s argument%s', self::localeFunc($functionName), (($argCount == 0) ? 'no' : $argCount), (($argCount == 1) ? '' : 's'));
}
if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) { // function
$passByReference = false;
$passCellReference = false;
$functionCall = null;
if (isset(self::$phpSpreadsheetFunctions[$functionName])) {
$functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
$passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']);
$passCellReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passCellReference']);
} elseif (isset(self::$controlFunctions[$functionName])) {
$functionCall = self::$controlFunctions[$functionName]['functionCall'];
$passByReference = isset(self::$controlFunctions[$functionName]['passByReference']);
$passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']);
}
// get the arguments for this function
$args = $argArrayVals = [];
$emptyArguments = [];
for ($i = 0; $i < $argCount; ++$i) {
$arg = $stack->pop();
$a = $argCount - $i - 1;
if (
($passByReference)
&& (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a]))
&& (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])
) {
if ($arg['reference'] === null) {
$args[] = $cellID;
if ($functionName !== 'MKMATRIX') {
$argArrayVals[] = $this->showValue($cellID);
}
} else {
$args[] = $arg['reference'];
if ($functionName !== 'MKMATRIX') {
$argArrayVals[] = $this->showValue($arg['reference']);
}
}
} else {
if ($arg['type'] === 'Empty Argument' && in_array($functionName, ['MIN', 'MINA', 'MAX', 'MAXA', 'IF'], true)) {
$emptyArguments[] = false;
$args[] = $arg['value'] = 0;
$this->debugLog->writeDebugLog('Empty Argument reevaluated as 0');
} else {
$emptyArguments[] = $arg['type'] === 'Empty Argument';
$args[] = self::unwrapResult($arg['value']);
}
if ($functionName !== 'MKMATRIX') {
$argArrayVals[] = $this->showValue($arg['value']);
}
}
}
// Reverse the order of the arguments
krsort($args);
krsort($emptyArguments);
if ($argCount > 0 && is_array($functionCall)) {
$args = $this->addDefaultArgumentValues($functionCall, $args, $emptyArguments);
}
if (($passByReference) && ($argCount == 0)) {
$args[] = $cellID;
$argArrayVals[] = $this->showValue($cellID);
}
if ($functionName !== 'MKMATRIX') {
if ($this->debugLog->getWriteDebugLog()) {
krsort($argArrayVals);
$this->debugLog->writeDebugLog('Evaluating %s ( %s )', self::localeFunc($functionName), implode(self::$localeArgumentSeparator . ' ', Functions::flattenArray($argArrayVals)));
}
}
// Process the argument with the appropriate function call
$args = $this->addCellReference($args, $passCellReference, $functionCall, $cell);
if (!is_array($functionCall)) {
foreach ($args as &$arg) {
$arg = Functions::flattenSingleValue($arg);
}
unset($arg);
}
$result = call_user_func_array($functionCall, $args);
if ($functionName !== 'MKMATRIX') {
$this->debugLog->writeDebugLog('Evaluation Result for %s() function call is %s', self::localeFunc($functionName), $this->showTypeDetails($result));
}
$stack->push('Value', self::wrapResult($result));
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
}
} else {
// if the token is a number, boolean, string or an Excel error, push it onto the stack
if (isset(self::$excelConstants[strtoupper($token ?? '')])) {
$excelConstant = strtoupper($token);
$stack->push('Constant Value', self::$excelConstants[$excelConstant]);
if (isset($storeKey)) {
$branchStore[$storeKey] = self::$excelConstants[$excelConstant];
}
$this->debugLog->writeDebugLog('Evaluating Constant %s as %s', $excelConstant, $this->showTypeDetails(self::$excelConstants[$excelConstant]));
} elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == self::FORMULA_STRING_QUOTE) || ($token[0] == '#')) {
$stack->push($tokenData['type'], $token, $tokenData['reference']);
if (isset($storeKey)) {
$branchStore[$storeKey] = $token;
}
} elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token, $matches)) {
// if the token is a named range or formula, evaluate it and push the result onto the stack
$definedName = $matches[6];
if ($cell === null || $pCellWorksheet === null) {
return $this->raiseFormulaError("undefined name '$token'");
}
$this->debugLog->writeDebugLog('Evaluating Defined Name %s', $definedName);
$namedRange = DefinedName::resolveName($definedName, $pCellWorksheet);
// If not Defined Name, try as Table.
if ($namedRange === null && $this->spreadsheet !== null) {
$table = $this->spreadsheet->getTableByName($definedName);
if ($table !== null) {
$tableRange = Coordinate::getRangeBoundaries($table->getRange());
if ($table->getShowHeaderRow()) {
++$tableRange[0][1];
}
if ($table->getShowTotalsRow()) {
--$tableRange[1][1];
}
$tableRangeString
= '$' . $tableRange[0][0]
. '$' . $tableRange[0][1]
. ':'
. '$' . $tableRange[1][0]
. '$' . $tableRange[1][1];
$namedRange = new NamedRange($definedName, $table->getWorksheet(), $tableRangeString);
}
}
if ($namedRange === null) {
return $this->raiseFormulaError("undefined name '$definedName'");
}
$result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
} else {
return $this->raiseFormulaError("undefined name '$token'");
}
}
}
// when we're out of tokens, the stack should have a single element, the final result
if ($stack->count() != 1) {
return $this->raiseFormulaError('internal error');
}
$output = $stack->pop();
$output = $output['value'];
return $output;
}
private function validateBinaryOperand(mixed &$operand, mixed &$stack): bool
{
if (is_array($operand)) {
if ((count($operand, COUNT_RECURSIVE) - count($operand)) == 1) {
do {
$operand = array_pop($operand);
} while (is_array($operand));
}
}
// Numbers, matrices and booleans can pass straight through, as they're already valid
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] == 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
if (!is_numeric($operand)) {
// If not a numeric, test to see if the value is an Excel error, and so can't be used in normal binary operations
if ($operand > '' && $operand[0] == '#') {
$stack->push('Value', $operand);
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($operand));
return false;
} elseif (Engine\FormattedNumber::convertToNumberIfFormatted($operand) === false) {
// If not a numeric, a fraction or a percentage, then it's a text string, and so can't be used in mathematical binary operations
$stack->push('Error', '#VALUE!');
$this->debugLog->writeDebugLog('Evaluation Result is a %s', $this->showTypeDetails('#VALUE!'));
return false;
}
}
}
// return a true if the value of the operand is one that we can use in normal binary mathematical operations
return true;
}
private function executeArrayComparison(mixed $operand1, mixed $operand2, string $operation, Stack &$stack, bool $recursingArrays): array
{
$result = [];
if (!is_array($operand2)) {
// Operand 1 is an array, Operand 2 is a scalar
foreach ($operand1 as $x => $operandData) {
$this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operandData), $operation, $this->showValue($operand2));
$this->executeBinaryComparisonOperation($operandData, $operand2, $operation, $stack);
$r = $stack->pop();
$result[$x] = $r['value'];
}
} elseif (!is_array($operand1)) {
// Operand 1 is a scalar, Operand 2 is an array
foreach ($operand2 as $x => $operandData) {
$this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operand1), $operation, $this->showValue($operandData));
$this->executeBinaryComparisonOperation($operand1, $operandData, $operation, $stack);
$r = $stack->pop();
$result[$x] = $r['value'];
}
} else {
// Operand 1 and Operand 2 are both arrays
if (!$recursingArrays) {
self::checkMatrixOperands($operand1, $operand2, 2);
}
foreach ($operand1 as $x => $operandData) {
$this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operandData), $operation, $this->showValue($operand2[$x]));
$this->executeBinaryComparisonOperation($operandData, $operand2[$x], $operation, $stack, true);
$r = $stack->pop();
$result[$x] = $r['value'];
}
}
// Log the result details
$this->debugLog->writeDebugLog('Comparison Evaluation Result is %s', $this->showTypeDetails($result));
// And push the result onto the stack
$stack->push('Array', $result);
return $result;
}
private function executeBinaryComparisonOperation(mixed $operand1, mixed $operand2, string $operation, Stack &$stack, bool $recursingArrays = false): array|bool
{
// If we're dealing with matrix operations, we want a matrix result
if ((is_array($operand1)) || (is_array($operand2))) {
return $this->executeArrayComparison($operand1, $operand2, $operation, $stack, $recursingArrays);
}
$result = BinaryComparison::compare($operand1, $operand2, $operation);
// Log the result details
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result));
// And push the result onto the stack
$stack->push('Value', $result);
return $result;
}
private function executeNumericBinaryOperation(mixed $operand1, mixed $operand2, string $operation, Stack &$stack): mixed
{
// Validate the two operands
if (
($this->validateBinaryOperand($operand1, $stack) === false)
|| ($this->validateBinaryOperand($operand2, $stack) === false)
) {
return false;
}
if (
(Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE)
&& ((is_string($operand1) && !is_numeric($operand1) && $operand1 !== '')
|| (is_string($operand2) && !is_numeric($operand2) && $operand2 !== ''))
) {
$result = ExcelError::VALUE();
} elseif (is_array($operand1) || is_array($operand2)) {
// Ensure that both operands are arrays/matrices
if (is_array($operand1)) {
foreach ($operand1 as $key => $value) {
$operand1[$key] = Functions::flattenArray($value);
}
}
if (is_array($operand2)) {
foreach ($operand2 as $key => $value) {
$operand2[$key] = Functions::flattenArray($value);
}
}
[$rows, $columns] = self::checkMatrixOperands($operand1, $operand2, 2);
for ($row = 0; $row < $rows; ++$row) {
for ($column = 0; $column < $columns; ++$column) {
if ($operand1[$row][$column] === null) {
$operand1[$row][$column] = 0;
} elseif (!self::isNumericOrBool($operand1[$row][$column])) {
$operand1[$row][$column] = self::makeError($operand1[$row][$column]);
continue;
}
if ($operand2[$row][$column] === null) {
$operand2[$row][$column] = 0;
} elseif (!self::isNumericOrBool($operand2[$row][$column])) {
$operand1[$row][$column] = self::makeError($operand2[$row][$column]);
continue;
}
switch ($operation) {
case '+':
$operand1[$row][$column] += $operand2[$row][$column];
break;
case '-':
$operand1[$row][$column] -= $operand2[$row][$column];
break;
case '*':
$operand1[$row][$column] *= $operand2[$row][$column];
break;
case '/':
if ($operand2[$row][$column] == 0) {
$operand1[$row][$column] = ExcelError::DIV0();
} else {
$operand1[$row][$column] /= $operand2[$row][$column];
}
break;
case '^':
$operand1[$row][$column] = $operand1[$row][$column] ** $operand2[$row][$column];
break;
default:
throw new Exception('Unsupported numeric binary operation');
}
}
}
$result = $operand1;
} else {
// If we're dealing with non-matrix operations, execute the necessary operation
switch ($operation) {
// Addition
case '+':
$result = $operand1 + $operand2;
break;
// Subtraction
case '-':
$result = $operand1 - $operand2;
break;
// Multiplication
case '*':
$result = $operand1 * $operand2;
break;
// Division
case '/':
if ($operand2 == 0) {
// Trap for Divide by Zero error
$stack->push('Error', ExcelError::DIV0());
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails(ExcelError::DIV0()));
return false;
}
$result = $operand1 / $operand2;
break;
// Power
case '^':
$result = $operand1 ** $operand2;
break;
default:
throw new Exception('Unsupported numeric binary operation');
}
}
// Log the result details
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result));
// And push the result onto the stack
$stack->push('Value', $result);
return $result;
}
/**
* Trigger an error, but nicely, if need be.
*
* @return false
*/
protected function raiseFormulaError(string $errorMessage, int $code = 0, ?Throwable $exception = null): bool
{
$this->formulaError = $errorMessage;
$this->cyclicReferenceStack->clear();
$suppress = $this->suppressFormulaErrors ?? $this->suppressFormulaErrorsNew;
if (!$suppress) {
throw new Exception($errorMessage, $code, $exception);
}
return false;
}
/**
* Extract range values.
*
* @param string $range String based range representation
* @param ?Worksheet $worksheet Worksheet
* @param bool $resetLog Flag indicating whether calculation log should be reset or not
*
* @return array Array of values in range if range contains more than one element. Otherwise, a single value is returned.
*/
public function extractCellRange(string &$range = 'A1', ?Worksheet $worksheet = null, bool $resetLog = true): array
{
// Return value
$returnValue = [];
if ($worksheet !== null) {
$worksheetName = $worksheet->getTitle();
if (str_contains($range, '!')) {
[$worksheetName, $range] = Worksheet::extractSheetTitle($range, true);
$worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName);
}
// Extract range
$aReferences = Coordinate::extractAllCellReferencesInRange($range);
$range = "'" . $worksheetName . "'" . '!' . $range;
$currentCol = '';
$currentRow = 0;
if (!isset($aReferences[1])) {
// Single cell in range
sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow);
if ($worksheet !== null && $worksheet->cellExists($aReferences[0])) {
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
} else {
$returnValue[$currentRow][$currentCol] = null;
}
} else {
// Extract cell data for all cells in the range
foreach ($aReferences as $reference) {
// Extract range
sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow);
if ($worksheet !== null && $worksheet->cellExists($reference)) {
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($reference)->getCalculatedValue($resetLog);
} else {
$returnValue[$currentRow][$currentCol] = null;
}
}
}
}
return $returnValue;
}
/**
* Extract range values.
*
* @param string $range String based range representation
* @param null|Worksheet $worksheet Worksheet
* @param bool $resetLog Flag indicating whether calculation log should be reset or not
*
* @return array|string Array of values in range if range contains more than one element. Otherwise, a single value is returned.
*/
public function extractNamedRange(string &$range = 'A1', ?Worksheet $worksheet = null, bool $resetLog = true): string|array
{
// Return value
$returnValue = [];
if ($worksheet !== null) {
if (str_contains($range, '!')) {
[$worksheetName, $range] = Worksheet::extractSheetTitle($range, true);
$worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName);
}
// Named range?
$namedRange = ($worksheet === null) ? null : DefinedName::resolveName($range, $worksheet);
if ($namedRange === null) {
return ExcelError::REF();
}
$worksheet = $namedRange->getWorksheet();
$range = $namedRange->getValue();
$splitRange = Coordinate::splitRange($range);
// Convert row and column references
if ($worksheet !== null && ctype_alpha($splitRange[0][0])) {
$range = $splitRange[0][0] . '1:' . $splitRange[0][1] . $worksheet->getHighestRow();
} elseif ($worksheet !== null && ctype_digit($splitRange[0][0])) {
$range = 'A' . $splitRange[0][0] . ':' . $worksheet->getHighestColumn() . $splitRange[0][1];
}
// Extract range
$aReferences = Coordinate::extractAllCellReferencesInRange($range);
if (!isset($aReferences[1])) {
// Single cell (or single column or row) in range
[$currentCol, $currentRow] = Coordinate::coordinateFromString($aReferences[0]);
if ($worksheet !== null && $worksheet->cellExists($aReferences[0])) {
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
} else {
$returnValue[$currentRow][$currentCol] = null;
}
} else {
// Extract cell data for all cells in the range
foreach ($aReferences as $reference) {
// Extract range
[$currentCol, $currentRow] = Coordinate::coordinateFromString($reference);
if ($worksheet !== null && $worksheet->cellExists($reference)) {
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($reference)->getCalculatedValue($resetLog);
} else {
$returnValue[$currentRow][$currentCol] = null;
}
}
}
}
return $returnValue;
}
/**
* Is a specific function implemented?
*
* @param string $function Function Name
*/
public function isImplemented(string $function): bool
{
$function = strtoupper($function);
$notImplemented = !isset(self::$phpSpreadsheetFunctions[$function]) || (is_array(self::$phpSpreadsheetFunctions[$function]['functionCall']) && self::$phpSpreadsheetFunctions[$function]['functionCall'][1] === 'DUMMY');
return !$notImplemented;
}
/**
* Get a list of all implemented functions as an array of function objects.
*/
public static function getFunctions(): array
{
return self::$phpSpreadsheetFunctions;
}
/**
* Get a list of implemented Excel function names.
*/
public function getImplementedFunctionNames(): array
{
$returnValue = [];
foreach (self::$phpSpreadsheetFunctions as $functionName => $function) {
if ($this->isImplemented($functionName)) {
$returnValue[] = $functionName;
}
}
return $returnValue;
}
private function addDefaultArgumentValues(array $functionCall, array $args, array $emptyArguments): array
{
$reflector = new ReflectionMethod($functionCall[0], $functionCall[1]);
$methodArguments = $reflector->getParameters();
if (count($methodArguments) > 0) {
// Apply any defaults for empty argument values
foreach ($emptyArguments as $argumentId => $isArgumentEmpty) {
if ($isArgumentEmpty === true) {
$reflectedArgumentId = count($args) - (int) $argumentId - 1;
if (
!array_key_exists($reflectedArgumentId, $methodArguments)
|| $methodArguments[$reflectedArgumentId]->isVariadic()
) {
break;
}
$args[$argumentId] = $this->getArgumentDefaultValue($methodArguments[$reflectedArgumentId]);
}
}
}
return $args;
}
private function getArgumentDefaultValue(ReflectionParameter $methodArgument): mixed
{
$defaultValue = null;
if ($methodArgument->isDefaultValueAvailable()) {
$defaultValue = $methodArgument->getDefaultValue();
if ($methodArgument->isDefaultValueConstant()) {
$constantName = $methodArgument->getDefaultValueConstantName() ?? '';
// read constant value
if (str_contains($constantName, '::')) {
[$className, $constantName] = explode('::', $constantName);
$constantReflector = new ReflectionClassConstant($className, $constantName);
return $constantReflector->getValue();
}
return constant($constantName);
}
}
return $defaultValue;
}
/**
* Add cell reference if needed while making sure that it is the last argument.
*/
private function addCellReference(array $args, bool $passCellReference, array|string $functionCall, ?Cell $cell = null): array
{
if ($passCellReference) {
if (is_array($functionCall)) {
$className = $functionCall[0];
$methodName = $functionCall[1];
$reflectionMethod = new ReflectionMethod($className, $methodName);
$argumentCount = count($reflectionMethod->getParameters());
while (count($args) < $argumentCount - 1) {
$args[] = null;
}
}
$args[] = $cell;
}
return $args;
}
private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksheet $cellWorksheet, Stack $stack): mixed
{
$definedNameScope = $namedRange->getScope();
if ($definedNameScope !== null && $definedNameScope !== $cellWorksheet) {
// The defined name isn't in our current scope, so #REF
$result = ExcelError::REF();
$stack->push('Error', $result, $namedRange->getName());
return $result;
}
$definedNameValue = $namedRange->getValue();
$definedNameType = $namedRange->isFormula() ? 'Formula' : 'Range';
$definedNameWorksheet = $namedRange->getWorksheet();
if ($definedNameValue[0] !== '=') {
$definedNameValue = '=' . $definedNameValue;
}
$this->debugLog->writeDebugLog('Defined Name is a %s with a value of %s', $definedNameType, $definedNameValue);
$originalCoordinate = $cell->getCoordinate();
$recursiveCalculationCell = ($definedNameType !== 'Formula' && $definedNameWorksheet !== null && $definedNameWorksheet !== $cellWorksheet)
? $definedNameWorksheet->getCell('A1')
: $cell;
$recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate();
// Adjust relative references in ranges and formulae so that we execute the calculation for the correct rows and columns
$definedNameValue = self::$referenceHelper->updateFormulaReferencesAnyWorksheet(
$definedNameValue,
Coordinate::columnIndexFromString($cell->getColumn()) - 1,
$cell->getRow() - 1
);
$this->debugLog->writeDebugLog('Value adjusted for relative references is %s', $definedNameValue);
$recursiveCalculator = new self($this->spreadsheet);
$recursiveCalculator->getDebugLog()->setWriteDebugLog($this->getDebugLog()->getWriteDebugLog());
$recursiveCalculator->getDebugLog()->setEchoDebugLog($this->getDebugLog()->getEchoDebugLog());
$result = $recursiveCalculator->_calculateFormulaValue($definedNameValue, $recursiveCalculationCellAddress, $recursiveCalculationCell, true);
$cellWorksheet->getCell($originalCoordinate);
if ($this->getDebugLog()->getWriteDebugLog()) {
$this->debugLog->mergeDebugLog(array_slice($recursiveCalculator->getDebugLog()->getLog(), 3));
$this->debugLog->writeDebugLog('Evaluation Result for Named %s %s is %s', $definedNameType, $namedRange->getName(), $this->showTypeDetails($result));
}
$stack->push('Defined Name', $result, $namedRange->getName());
return $result;
}
public function setSuppressFormulaErrors(bool $suppressFormulaErrors): void
{
$this->suppressFormulaErrorsNew = $suppressFormulaErrors;
}
public function getSuppressFormulaErrors(): bool
{
return $this->suppressFormulaErrorsNew;
}
private static function boolToString(mixed $operand1): mixed
{
if (is_bool($operand1)) {
$operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
} elseif ($operand1 === null) {
$operand1 = '';
}
return $operand1;
}
private static function isNumericOrBool(mixed $operand): bool
{
return is_numeric($operand) || is_bool($operand);
}
private static function makeError(mixed $operand = ''): string
{
return Information\ErrorValue::isError($operand) ? $operand : ExcelError::VALUE();
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions 0000644 00000025234 15167673465 0021367 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Italiano (Italian)
##
############################################################
##
## Funzioni cubo (Cube Functions)
##
CUBEKPIMEMBER = MEMBRO.KPI.CUBO
CUBEMEMBER = MEMBRO.CUBO
CUBEMEMBERPROPERTY = PROPRIETÀ.MEMBRO.CUBO
CUBERANKEDMEMBER = MEMBRO.CUBO.CON.RANGO
CUBESET = SET.CUBO
CUBESETCOUNT = CONTA.SET.CUBO
CUBEVALUE = VALORE.CUBO
##
## Funzioni di database (Database Functions)
##
DAVERAGE = DB.MEDIA
DCOUNT = DB.CONTA.NUMERI
DCOUNTA = DB.CONTA.VALORI
DGET = DB.VALORI
DMAX = DB.MAX
DMIN = DB.MIN
DPRODUCT = DB.PRODOTTO
DSTDEV = DB.DEV.ST
DSTDEVP = DB.DEV.ST.POP
DSUM = DB.SOMMA
DVAR = DB.VAR
DVARP = DB.VAR.POP
##
## Funzioni data e ora (Date & Time Functions)
##
DATE = DATA
DATEDIF = DATA.DIFF
DATESTRING = DATA.STRINGA
DATEVALUE = DATA.VALORE
DAY = GIORNO
DAYS = GIORNI
DAYS360 = GIORNO360
EDATE = DATA.MESE
EOMONTH = FINE.MESE
HOUR = ORA
ISOWEEKNUM = NUM.SETTIMANA.ISO
MINUTE = MINUTO
MONTH = MESE
NETWORKDAYS = GIORNI.LAVORATIVI.TOT
NETWORKDAYS.INTL = GIORNI.LAVORATIVI.TOT.INTL
NOW = ADESSO
SECOND = SECONDO
THAIDAYOFWEEK = THAIGIORNODELLASETTIMANA
THAIMONTHOFYEAR = THAIMESEDELLANNO
THAIYEAR = THAIANNO
TIME = ORARIO
TIMEVALUE = ORARIO.VALORE
TODAY = OGGI
WEEKDAY = GIORNO.SETTIMANA
WEEKNUM = NUM.SETTIMANA
WORKDAY = GIORNO.LAVORATIVO
WORKDAY.INTL = GIORNO.LAVORATIVO.INTL
YEAR = ANNO
YEARFRAC = FRAZIONE.ANNO
##
## Funzioni ingegneristiche (Engineering Functions)
##
BESSELI = BESSEL.I
BESSELJ = BESSEL.J
BESSELK = BESSEL.K
BESSELY = BESSEL.Y
BIN2DEC = BINARIO.DECIMALE
BIN2HEX = BINARIO.HEX
BIN2OCT = BINARIO.OCT
BITAND = BITAND
BITLSHIFT = BIT.SPOSTA.SX
BITOR = BITOR
BITRSHIFT = BIT.SPOSTA.DX
BITXOR = BITXOR
COMPLEX = COMPLESSO
CONVERT = CONVERTI
DEC2BIN = DECIMALE.BINARIO
DEC2HEX = DECIMALE.HEX
DEC2OCT = DECIMALE.OCT
DELTA = DELTA
ERF = FUNZ.ERRORE
ERF.PRECISE = FUNZ.ERRORE.PRECISA
ERFC = FUNZ.ERRORE.COMP
ERFC.PRECISE = FUNZ.ERRORE.COMP.PRECISA
GESTEP = SOGLIA
HEX2BIN = HEX.BINARIO
HEX2DEC = HEX.DECIMALE
HEX2OCT = HEX.OCT
IMABS = COMP.MODULO
IMAGINARY = COMP.IMMAGINARIO
IMARGUMENT = COMP.ARGOMENTO
IMCONJUGATE = COMP.CONIUGATO
IMCOS = COMP.COS
IMCOSH = COMP.COSH
IMCOT = COMP.COT
IMCSC = COMP.CSC
IMCSCH = COMP.CSCH
IMDIV = COMP.DIV
IMEXP = COMP.EXP
IMLN = COMP.LN
IMLOG10 = COMP.LOG10
IMLOG2 = COMP.LOG2
IMPOWER = COMP.POTENZA
IMPRODUCT = COMP.PRODOTTO
IMREAL = COMP.PARTE.REALE
IMSEC = COMP.SEC
IMSECH = COMP.SECH
IMSIN = COMP.SEN
IMSINH = COMP.SENH
IMSQRT = COMP.RADQ
IMSUB = COMP.DIFF
IMSUM = COMP.SOMMA
IMTAN = COMP.TAN
OCT2BIN = OCT.BINARIO
OCT2DEC = OCT.DECIMALE
OCT2HEX = OCT.HEX
##
## Funzioni finanziarie (Financial Functions)
##
ACCRINT = INT.MATURATO.PER
ACCRINTM = INT.MATURATO.SCAD
AMORDEGRC = AMMORT.DEGR
AMORLINC = AMMORT.PER
COUPDAYBS = GIORNI.CED.INIZ.LIQ
COUPDAYS = GIORNI.CED
COUPDAYSNC = GIORNI.CED.NUOVA
COUPNCD = DATA.CED.SUCC
COUPNUM = NUM.CED
COUPPCD = DATA.CED.PREC
CUMIPMT = INT.CUMUL
CUMPRINC = CAP.CUM
DB = AMMORT.FISSO
DDB = AMMORT
DISC = TASSO.SCONTO
DOLLARDE = VALUTA.DEC
DOLLARFR = VALUTA.FRAZ
DURATION = DURATA
EFFECT = EFFETTIVO
FV = VAL.FUT
FVSCHEDULE = VAL.FUT.CAPITALE
INTRATE = TASSO.INT
IPMT = INTERESSI
IRR = TIR.COST
ISPMT = INTERESSE.RATA
MDURATION = DURATA.M
MIRR = TIR.VAR
NOMINAL = NOMINALE
NPER = NUM.RATE
NPV = VAN
ODDFPRICE = PREZZO.PRIMO.IRR
ODDFYIELD = REND.PRIMO.IRR
ODDLPRICE = PREZZO.ULTIMO.IRR
ODDLYIELD = REND.ULTIMO.IRR
PDURATION = DURATA.P
PMT = RATA
PPMT = P.RATA
PRICE = PREZZO
PRICEDISC = PREZZO.SCONT
PRICEMAT = PREZZO.SCAD
PV = VA
RATE = TASSO
RECEIVED = RICEV.SCAD
RRI = RIT.INVEST.EFFETT
SLN = AMMORT.COST
SYD = AMMORT.ANNUO
TBILLEQ = BOT.EQUIV
TBILLPRICE = BOT.PREZZO
TBILLYIELD = BOT.REND
VDB = AMMORT.VAR
XIRR = TIR.X
XNPV = VAN.X
YIELD = REND
YIELDDISC = REND.TITOLI.SCONT
YIELDMAT = REND.SCAD
##
## Funzioni relative alle informazioni (Information Functions)
##
CELL = CELLA
ERROR.TYPE = ERRORE.TIPO
INFO = AMBIENTE.INFO
ISBLANK = VAL.VUOTO
ISERR = VAL.ERR
ISERROR = VAL.ERRORE
ISEVEN = VAL.PARI
ISFORMULA = VAL.FORMULA
ISLOGICAL = VAL.LOGICO
ISNA = VAL.NON.DISP
ISNONTEXT = VAL.NON.TESTO
ISNUMBER = VAL.NUMERO
ISODD = VAL.DISPARI
ISREF = VAL.RIF
ISTEXT = VAL.TESTO
N = NUM
NA = NON.DISP
SHEET = FOGLIO
SHEETS = FOGLI
TYPE = TIPO
##
## Funzioni logiche (Logical Functions)
##
AND = E
FALSE = FALSO
IF = SE
IFERROR = SE.ERRORE
IFNA = SE.NON.DISP.
IFS = PIÙ.SE
NOT = NON
OR = O
SWITCH = SWITCH
TRUE = VERO
XOR = XOR
##
## Funzioni di ricerca e di riferimento (Lookup & Reference Functions)
##
ADDRESS = INDIRIZZO
AREAS = AREE
CHOOSE = SCEGLI
COLUMN = RIF.COLONNA
COLUMNS = COLONNE
FORMULATEXT = TESTO.FORMULA
GETPIVOTDATA = INFO.DATI.TAB.PIVOT
HLOOKUP = CERCA.ORIZZ
HYPERLINK = COLLEG.IPERTESTUALE
INDEX = INDICE
INDIRECT = INDIRETTO
LOOKUP = CERCA
MATCH = CONFRONTA
OFFSET = SCARTO
ROW = RIF.RIGA
ROWS = RIGHE
RTD = DATITEMPOREALE
TRANSPOSE = MATR.TRASPOSTA
VLOOKUP = CERCA.VERT
##
## Funzioni matematiche e trigonometriche (Math & Trig Functions)
##
ABS = ASS
ACOS = ARCCOS
ACOSH = ARCCOSH
ACOT = ARCCOT
ACOTH = ARCCOTH
AGGREGATE = AGGREGA
ARABIC = ARABO
ASIN = ARCSEN
ASINH = ARCSENH
ATAN = ARCTAN
ATAN2 = ARCTAN.2
ATANH = ARCTANH
BASE = BASE
CEILING.MATH = ARROTONDA.ECCESSO.MAT
CEILING.PRECISE = ARROTONDA.ECCESSO.PRECISA
COMBIN = COMBINAZIONE
COMBINA = COMBINAZIONE.VALORI
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DECIMALE
DEGREES = GRADI
ECMA.CEILING = ECMA.ARROTONDA.ECCESSO
EVEN = PARI
EXP = EXP
FACT = FATTORIALE
FACTDOUBLE = FATT.DOPPIO
FLOOR.MATH = ARROTONDA.DIFETTO.MAT
FLOOR.PRECISE = ARROTONDA.DIFETTO.PRECISA
GCD = MCD
INT = INT
ISO.CEILING = ISO.ARROTONDA.ECCESSO
LCM = MCM
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MATR.DETERM
MINVERSE = MATR.INVERSA
MMULT = MATR.PRODOTTO
MOD = RESTO
MROUND = ARROTONDA.MULTIPLO
MULTINOMIAL = MULTINOMIALE
MUNIT = MATR.UNIT
ODD = DISPARI
PI = PI.GRECO
POWER = POTENZA
PRODUCT = PRODOTTO
QUOTIENT = QUOZIENTE
RADIANS = RADIANTI
RAND = CASUALE
RANDBETWEEN = CASUALE.TRA
ROMAN = ROMANO
ROUND = ARROTONDA
ROUNDBAHTDOWN = ARROTBAHTGIU
ROUNDBAHTUP = ARROTBAHTSU
ROUNDDOWN = ARROTONDA.PER.DIF
ROUNDUP = ARROTONDA.PER.ECC
SEC = SEC
SECH = SECH
SERIESSUM = SOMMA.SERIE
SIGN = SEGNO
SIN = SEN
SINH = SENH
SQRT = RADQ
SQRTPI = RADQ.PI.GRECO
SUBTOTAL = SUBTOTALE
SUM = SOMMA
SUMIF = SOMMA.SE
SUMIFS = SOMMA.PIÙ.SE
SUMPRODUCT = MATR.SOMMA.PRODOTTO
SUMSQ = SOMMA.Q
SUMX2MY2 = SOMMA.DIFF.Q
SUMX2PY2 = SOMMA.SOMMA.Q
SUMXMY2 = SOMMA.Q.DIFF
TAN = TAN
TANH = TANH
TRUNC = TRONCA
##
## Funzioni statistiche (Statistical Functions)
##
AVEDEV = MEDIA.DEV
AVERAGE = MEDIA
AVERAGEA = MEDIA.VALORI
AVERAGEIF = MEDIA.SE
AVERAGEIFS = MEDIA.PIÙ.SE
BETA.DIST = DISTRIB.BETA.N
BETA.INV = INV.BETA.N
BINOM.DIST = DISTRIB.BINOM.N
BINOM.DIST.RANGE = INTERVALLO.DISTRIB.BINOM.N.
BINOM.INV = INV.BINOM
CHISQ.DIST = DISTRIB.CHI.QUAD
CHISQ.DIST.RT = DISTRIB.CHI.QUAD.DS
CHISQ.INV = INV.CHI.QUAD
CHISQ.INV.RT = INV.CHI.QUAD.DS
CHISQ.TEST = TEST.CHI.QUAD
CONFIDENCE.NORM = CONFIDENZA.NORM
CONFIDENCE.T = CONFIDENZA.T
CORREL = CORRELAZIONE
COUNT = CONTA.NUMERI
COUNTA = CONTA.VALORI
COUNTBLANK = CONTA.VUOTE
COUNTIF = CONTA.SE
COUNTIFS = CONTA.PIÙ.SE
COVARIANCE.P = COVARIANZA.P
COVARIANCE.S = COVARIANZA.C
DEVSQ = DEV.Q
EXPON.DIST = DISTRIB.EXP.N
F.DIST = DISTRIBF
F.DIST.RT = DISTRIB.F.DS
F.INV = INVF
F.INV.RT = INV.F.DS
F.TEST = TESTF
FISHER = FISHER
FISHERINV = INV.FISHER
FORECAST.ETS = PREVISIONE.ETS
FORECAST.ETS.CONFINT = PREVISIONE.ETS.INTCONF
FORECAST.ETS.SEASONALITY = PREVISIONE.ETS.STAGIONALITÀ
FORECAST.ETS.STAT = PREVISIONE.ETS.STAT
FORECAST.LINEAR = PREVISIONE.LINEARE
FREQUENCY = FREQUENZA
GAMMA = GAMMA
GAMMA.DIST = DISTRIB.GAMMA.N
GAMMA.INV = INV.GAMMA.N
GAMMALN = LN.GAMMA
GAMMALN.PRECISE = LN.GAMMA.PRECISA
GAUSS = GAUSS
GEOMEAN = MEDIA.GEOMETRICA
GROWTH = CRESCITA
HARMEAN = MEDIA.ARMONICA
HYPGEOM.DIST = DISTRIB.IPERGEOM.N
INTERCEPT = INTERCETTA
KURT = CURTOSI
LARGE = GRANDE
LINEST = REGR.LIN
LOGEST = REGR.LOG
LOGNORM.DIST = DISTRIB.LOGNORM.N
LOGNORM.INV = INV.LOGNORM.N
MAX = MAX
MAXA = MAX.VALORI
MAXIFS = MAX.PIÙ.SE
MEDIAN = MEDIANA
MIN = MIN
MINA = MIN.VALORI
MINIFS = MIN.PIÙ.SE
MODE.MULT = MODA.MULT
MODE.SNGL = MODA.SNGL
NEGBINOM.DIST = DISTRIB.BINOM.NEG.N
NORM.DIST = DISTRIB.NORM.N
NORM.INV = INV.NORM.N
NORM.S.DIST = DISTRIB.NORM.ST.N
NORM.S.INV = INV.NORM.S
PEARSON = PEARSON
PERCENTILE.EXC = ESC.PERCENTILE
PERCENTILE.INC = INC.PERCENTILE
PERCENTRANK.EXC = ESC.PERCENT.RANGO
PERCENTRANK.INC = INC.PERCENT.RANGO
PERMUT = PERMUTAZIONE
PERMUTATIONA = PERMUTAZIONE.VALORI
PHI = PHI
POISSON.DIST = DISTRIB.POISSON
PROB = PROBABILITÀ
QUARTILE.EXC = ESC.QUARTILE
QUARTILE.INC = INC.QUARTILE
RANK.AVG = RANGO.MEDIA
RANK.EQ = RANGO.UG
RSQ = RQ
SKEW = ASIMMETRIA
SKEW.P = ASIMMETRIA.P
SLOPE = PENDENZA
SMALL = PICCOLO
STANDARDIZE = NORMALIZZA
STDEV.P = DEV.ST.P
STDEV.S = DEV.ST.C
STDEVA = DEV.ST.VALORI
STDEVPA = DEV.ST.POP.VALORI
STEYX = ERR.STD.YX
T.DIST = DISTRIB.T.N
T.DIST.2T = DISTRIB.T.2T
T.DIST.RT = DISTRIB.T.DS
T.INV = INVT
T.INV.2T = INV.T.2T
T.TEST = TESTT
TREND = TENDENZA
TRIMMEAN = MEDIA.TRONCATA
VAR.P = VAR.P
VAR.S = VAR.C
VARA = VAR.VALORI
VARPA = VAR.POP.VALORI
WEIBULL.DIST = DISTRIB.WEIBULL
Z.TEST = TESTZ
##
## Funzioni di testo (Text Functions)
##
BAHTTEXT = BAHTTESTO
CHAR = CODICE.CARATT
CLEAN = LIBERA
CODE = CODICE
CONCAT = CONCAT
DOLLAR = VALUTA
EXACT = IDENTICO
FIND = TROVA
FIXED = FISSO
ISTHAIDIGIT = ÈTHAICIFRA
LEFT = SINISTRA
LEN = LUNGHEZZA
LOWER = MINUSC
MID = STRINGA.ESTRAI
NUMBERSTRING = NUMERO.STRINGA
NUMBERVALUE = NUMERO.VALORE
PHONETIC = FURIGANA
PROPER = MAIUSC.INIZ
REPLACE = RIMPIAZZA
REPT = RIPETI
RIGHT = DESTRA
SEARCH = RICERCA
SUBSTITUTE = SOSTITUISCI
T = T
TEXT = TESTO
TEXTJOIN = TESTO.UNISCI
THAIDIGIT = THAICIFRA
THAINUMSOUND = THAINUMSUONO
THAINUMSTRING = THAISZÁMKAR
THAISTRINGLENGTH = THAILUNGSTRINGA
TRIM = ANNULLA.SPAZI
UNICHAR = CARATT.UNI
UNICODE = UNICODE
UPPER = MAIUSC
VALUE = VALORE
##
## Funzioni Web (Web Functions)
##
ENCODEURL = CODIFICA.URL
FILTERXML = FILTRO.XML
WEBSERVICE = SERVIZIO.WEB
##
## Funzioni di compatibilità (Compatibility Functions)
##
BETADIST = DISTRIB.BETA
BETAINV = INV.BETA
BINOMDIST = DISTRIB.BINOM
CEILING = ARROTONDA.ECCESSO
CHIDIST = DISTRIB.CHI
CHIINV = INV.CHI
CHITEST = TEST.CHI
CONCATENATE = CONCATENA
CONFIDENCE = CONFIDENZA
COVAR = COVARIANZA
CRITBINOM = CRIT.BINOM
EXPONDIST = DISTRIB.EXP
FDIST = DISTRIB.F
FINV = INV.F
FLOOR = ARROTONDA.DIFETTO
FORECAST = PREVISIONE
FTEST = TEST.F
GAMMADIST = DISTRIB.GAMMA
GAMMAINV = INV.GAMMA
HYPGEOMDIST = DISTRIB.IPERGEOM
LOGINV = INV.LOGNORM
LOGNORMDIST = DISTRIB.LOGNORM
MODE = MODA
NEGBINOMDIST = DISTRIB.BINOM.NEG
NORMDIST = DISTRIB.NORM
NORMINV = INV.NORM
NORMSDIST = DISTRIB.NORM.ST
NORMSINV = INV.NORM.ST
PERCENTILE = PERCENTILE
PERCENTRANK = PERCENT.RANGO
POISSON = POISSON
QUARTILE = QUARTILE
RANK = RANGO
STDEV = DEV.ST
STDEVP = DEV.ST.POP
TDIST = DISTRIB.T
TINV = INV.T
TTEST = TEST.T
VAR = VAR
VARP = VAR.POP
WEIBULL = WEIBULL
ZTEST = TEST.Z
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config 0000644 00000000455 15167673465 0020622 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Italiano (Italian)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL
DIV0
VALUE = #VALORE!
REF = #RIF!
NAME = #NOME?
NUM
NA = #N/D
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/functions 0000644 00000022470 15167673465 0021342 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## български (Bulgarian)
##
############################################################
##
## Функции Куб (Cube Functions)
##
CUBEKPIMEMBER = КУБЭЛЕМЕНТКИП
CUBEMEMBER = КУБЭЛЕМЕНТ
CUBEMEMBERPROPERTY = КУБСВОЙСТВОЭЛЕМЕНТА
CUBERANKEDMEMBER = КУБПОРЭЛЕМЕНТ
CUBESET = КУБМНОЖ
CUBESETCOUNT = КУБЧИСЛОЭЛМНОЖ
CUBEVALUE = КУБЗНАЧЕНИЕ
##
## Функции для работы с базами данных (Database Functions)
##
DAVERAGE = ДСРЗНАЧ
DCOUNT = БСЧЁТ
DCOUNTA = БСЧЁТА
DGET = БИЗВЛЕЧЬ
DMAX = ДМАКС
DMIN = ДМИН
DPRODUCT = БДПРОИЗВЕД
DSTDEV = ДСТАНДОТКЛ
DSTDEVP = ДСТАНДОТКЛП
DSUM = БДСУММ
DVAR = БДДИСП
DVARP = БДДИСПП
##
## Функции даты и времени (Date & Time Functions)
##
DATE = ДАТА
DATEVALUE = ДАТАЗНАЧ
DAY = ДЕНЬ
DAYS360 = ДНЕЙ360
EDATE = ДАТАМЕС
EOMONTH = КОНМЕСЯЦА
HOUR = ЧАС
MINUTE = МИНУТЫ
MONTH = МЕСЯЦ
NETWORKDAYS = ЧИСТРАБДНИ
NOW = ТДАТА
SECOND = СЕКУНДЫ
TIME = ВРЕМЯ
TIMEVALUE = ВРЕМЗНАЧ
TODAY = СЕГОДНЯ
WEEKDAY = ДЕНЬНЕД
WEEKNUM = НОМНЕДЕЛИ
WORKDAY = РАБДЕНЬ
YEAR = ГОД
YEARFRAC = ДОЛЯГОДА
##
## Инженерные функции (Engineering Functions)
##
BESSELI = БЕССЕЛЬ.I
BESSELJ = БЕССЕЛЬ.J
BESSELK = БЕССЕЛЬ.K
BESSELY = БЕССЕЛЬ.Y
BIN2DEC = ДВ.В.ДЕС
BIN2HEX = ДВ.В.ШЕСТН
BIN2OCT = ДВ.В.ВОСЬМ
COMPLEX = КОМПЛЕКСН
CONVERT = ПРЕОБР
DEC2BIN = ДЕС.В.ДВ
DEC2HEX = ДЕС.В.ШЕСТН
DEC2OCT = ДЕС.В.ВОСЬМ
DELTA = ДЕЛЬТА
ERF = ФОШ
ERFC = ДФОШ
GESTEP = ПОРОГ
HEX2BIN = ШЕСТН.В.ДВ
HEX2DEC = ШЕСТН.В.ДЕС
HEX2OCT = ШЕСТН.В.ВОСЬМ
IMABS = МНИМ.ABS
IMAGINARY = МНИМ.ЧАСТЬ
IMARGUMENT = МНИМ.АРГУМЕНТ
IMCONJUGATE = МНИМ.СОПРЯЖ
IMCOS = МНИМ.COS
IMDIV = МНИМ.ДЕЛ
IMEXP = МНИМ.EXP
IMLN = МНИМ.LN
IMLOG10 = МНИМ.LOG10
IMLOG2 = МНИМ.LOG2
IMPOWER = МНИМ.СТЕПЕНЬ
IMPRODUCT = МНИМ.ПРОИЗВЕД
IMREAL = МНИМ.ВЕЩ
IMSIN = МНИМ.SIN
IMSQRT = МНИМ.КОРЕНЬ
IMSUB = МНИМ.РАЗН
IMSUM = МНИМ.СУММ
OCT2BIN = ВОСЬМ.В.ДВ
OCT2DEC = ВОСЬМ.В.ДЕС
OCT2HEX = ВОСЬМ.В.ШЕСТН
##
## Финансовые функции (Financial Functions)
##
ACCRINT = НАКОПДОХОД
ACCRINTM = НАКОПДОХОДПОГАШ
AMORDEGRC = АМОРУМ
AMORLINC = АМОРУВ
COUPDAYBS = ДНЕЙКУПОНДО
COUPDAYS = ДНЕЙКУПОН
COUPDAYSNC = ДНЕЙКУПОНПОСЛЕ
COUPNCD = ДАТАКУПОНПОСЛЕ
COUPNUM = ЧИСЛКУПОН
COUPPCD = ДАТАКУПОНДО
CUMIPMT = ОБЩПЛАТ
CUMPRINC = ОБЩДОХОД
DB = ФУО
DDB = ДДОБ
DISC = СКИДКА
DOLLARDE = РУБЛЬ.ДЕС
DOLLARFR = РУБЛЬ.ДРОБЬ
DURATION = ДЛИТ
EFFECT = ЭФФЕКТ
FV = БС
FVSCHEDULE = БЗРАСПИС
INTRATE = ИНОРМА
IPMT = ПРПЛТ
IRR = ВСД
ISPMT = ПРОЦПЛАТ
MDURATION = МДЛИТ
MIRR = МВСД
NOMINAL = НОМИНАЛ
NPER = КПЕР
NPV = ЧПС
ODDFPRICE = ЦЕНАПЕРВНЕРЕГ
ODDFYIELD = ДОХОДПЕРВНЕРЕГ
ODDLPRICE = ЦЕНАПОСЛНЕРЕГ
ODDLYIELD = ДОХОДПОСЛНЕРЕГ
PMT = ПЛТ
PPMT = ОСПЛТ
PRICE = ЦЕНА
PRICEDISC = ЦЕНАСКИДКА
PRICEMAT = ЦЕНАПОГАШ
PV = ПС
RATE = СТАВКА
RECEIVED = ПОЛУЧЕНО
SLN = АПЛ
SYD = АСЧ
TBILLEQ = РАВНОКЧЕК
TBILLPRICE = ЦЕНАКЧЕК
TBILLYIELD = ДОХОДКЧЕК
VDB = ПУО
XIRR = ЧИСТВНДОХ
XNPV = ЧИСТНЗ
YIELD = ДОХОД
YIELDDISC = ДОХОДСКИДКА
YIELDMAT = ДОХОДПОГАШ
##
## Информационные функции (Information Functions)
##
CELL = ЯЧЕЙКА
ERROR.TYPE = ТИП.ОШИБКИ
INFO = ИНФОРМ
ISBLANK = ЕПУСТО
ISERR = ЕОШ
ISERROR = ЕОШИБКА
ISEVEN = ЕЧЁТН
ISLOGICAL = ЕЛОГИЧ
ISNA = ЕНД
ISNONTEXT = ЕНЕТЕКСТ
ISNUMBER = ЕЧИСЛО
ISODD = ЕНЕЧЁТ
ISREF = ЕССЫЛКА
ISTEXT = ЕТЕКСТ
N = Ч
NA = НД
TYPE = ТИП
##
## Логические функции (Logical Functions)
##
AND = И
FALSE = ЛОЖЬ
IF = ЕСЛИ
IFERROR = ЕСЛИОШИБКА
NOT = НЕ
OR = ИЛИ
TRUE = ИСТИНА
##
## Функции ссылки и поиска (Lookup & Reference Functions)
##
ADDRESS = АДРЕС
AREAS = ОБЛАСТИ
CHOOSE = ВЫБОР
COLUMN = СТОЛБЕЦ
COLUMNS = ЧИСЛСТОЛБ
GETPIVOTDATA = ПОЛУЧИТЬ.ДАННЫЕ.СВОДНОЙ.ТАБЛИЦЫ
HLOOKUP = ГПР
HYPERLINK = ГИПЕРССЫЛКА
INDEX = ИНДЕКС
INDIRECT = ДВССЫЛ
LOOKUP = ПРОСМОТР
MATCH = ПОИСКПОЗ
OFFSET = СМЕЩ
ROW = СТРОКА
ROWS = ЧСТРОК
RTD = ДРВ
TRANSPOSE = ТРАНСП
VLOOKUP = ВПР
##
## Математические и тригонометрические функции (Math & Trig Functions)
##
ABS = ABS
ACOS = ACOS
ACOSH = ACOSH
ASIN = ASIN
ASINH = ASINH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
COMBIN = ЧИСЛКОМБ
COS = COS
COSH = COSH
DEGREES = ГРАДУСЫ
EVEN = ЧЁТН
EXP = EXP
FACT = ФАКТР
FACTDOUBLE = ДВФАКТР
GCD = НОД
INT = ЦЕЛОЕ
LCM = НОК
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = МОПРЕД
MINVERSE = МОБР
MMULT = МУМНОЖ
MOD = ОСТАТ
MROUND = ОКРУГЛТ
MULTINOMIAL = МУЛЬТИНОМ
ODD = НЕЧЁТ
PI = ПИ
POWER = СТЕПЕНЬ
PRODUCT = ПРОИЗВЕД
QUOTIENT = ЧАСТНОЕ
RADIANS = РАДИАНЫ
RAND = СЛЧИС
RANDBETWEEN = СЛУЧМЕЖДУ
ROMAN = РИМСКОЕ
ROUND = ОКРУГЛ
ROUNDDOWN = ОКРУГЛВНИЗ
ROUNDUP = ОКРУГЛВВЕРХ
SERIESSUM = РЯД.СУММ
SIGN = ЗНАК
SIN = SIN
SINH = SINH
SQRT = КОРЕНЬ
SQRTPI = КОРЕНЬПИ
SUBTOTAL = ПРОМЕЖУТОЧНЫЕ.ИТОГИ
SUM = СУММ
SUMIF = СУММЕСЛИ
SUMIFS = СУММЕСЛИМН
SUMPRODUCT = СУММПРОИЗВ
SUMSQ = СУММКВ
SUMX2MY2 = СУММРАЗНКВ
SUMX2PY2 = СУММСУММКВ
SUMXMY2 = СУММКВРАЗН
TAN = TAN
TANH = TANH
TRUNC = ОТБР
##
## Статистические функции (Statistical Functions)
##
AVEDEV = СРОТКЛ
AVERAGE = СРЗНАЧ
AVERAGEA = СРЗНАЧА
AVERAGEIF = СРЗНАЧЕСЛИ
AVERAGEIFS = СРЗНАЧЕСЛИМН
CORREL = КОРРЕЛ
COUNT = СЧЁТ
COUNTA = СЧЁТЗ
COUNTBLANK = СЧИТАТЬПУСТОТЫ
COUNTIF = СЧЁТЕСЛИ
COUNTIFS = СЧЁТЕСЛИМН
DEVSQ = КВАДРОТКЛ
FISHER = ФИШЕР
FISHERINV = ФИШЕРОБР
FREQUENCY = ЧАСТОТА
GAMMALN = ГАММАНЛОГ
GEOMEAN = СРГЕОМ
GROWTH = РОСТ
HARMEAN = СРГАРМ
INTERCEPT = ОТРЕЗОК
KURT = ЭКСЦЕСС
LARGE = НАИБОЛЬШИЙ
LINEST = ЛИНЕЙН
LOGEST = ЛГРФПРИБЛ
MAX = МАКС
MAXA = МАКСА
MEDIAN = МЕДИАНА
MIN = МИН
MINA = МИНА
PEARSON = ПИРСОН
PERMUT = ПЕРЕСТ
PROB = ВЕРОЯТНОСТЬ
RSQ = КВПИРСОН
SKEW = СКОС
SLOPE = НАКЛОН
SMALL = НАИМЕНЬШИЙ
STANDARDIZE = НОРМАЛИЗАЦИЯ
STDEVA = СТАНДОТКЛОНА
STDEVPA = СТАНДОТКЛОНПА
STEYX = СТОШYX
TREND = ТЕНДЕНЦИЯ
TRIMMEAN = УРЕЗСРЕДНЕЕ
VARA = ДИСПА
VARPA = ДИСПРА
##
## Текстовые функции (Text Functions)
##
ASC = ASC
BAHTTEXT = БАТТЕКСТ
CHAR = СИМВОЛ
CLEAN = ПЕЧСИМВ
CODE = КОДСИМВ
DOLLAR = РУБЛЬ
EXACT = СОВПАД
FIND = НАЙТИ
FINDB = НАЙТИБ
FIXED = ФИКСИРОВАННЫЙ
LEFT = ЛЕВСИМВ
LEFTB = ЛЕВБ
LEN = ДЛСТР
LENB = ДЛИНБ
LOWER = СТРОЧН
MID = ПСТР
MIDB = ПСТРБ
PHONETIC = PHONETIC
PROPER = ПРОПНАЧ
REPLACE = ЗАМЕНИТЬ
REPLACEB = ЗАМЕНИТЬБ
REPT = ПОВТОР
RIGHT = ПРАВСИМВ
RIGHTB = ПРАВБ
SEARCH = ПОИСК
SEARCHB = ПОИСКБ
SUBSTITUTE = ПОДСТАВИТЬ
T = Т
TEXT = ТЕКСТ
TRIM = СЖПРОБЕЛЫ
UPPER = ПРОПИСН
VALUE = ЗНАЧЕН
##
## (Web Functions)
##
##
## (Compatibility Functions)
##
BETADIST = БЕТАРАСП
BETAINV = БЕТАОБР
BINOMDIST = БИНОМРАСП
CEILING = ОКРВВЕРХ
CHIDIST = ХИ2РАСП
CHIINV = ХИ2ОБР
CHITEST = ХИ2ТЕСТ
CONCATENATE = СЦЕПИТЬ
CONFIDENCE = ДОВЕРИТ
COVAR = КОВАР
CRITBINOM = КРИТБИНОМ
EXPONDIST = ЭКСПРАСП
FDIST = FРАСП
FINV = FРАСПОБР
FLOOR = ОКРВНИЗ
FORECAST = ПРЕДСКАЗ
FTEST = ФТЕСТ
GAMMADIST = ГАММАРАСП
GAMMAINV = ГАММАОБР
HYPGEOMDIST = ГИПЕРГЕОМЕТ
LOGINV = ЛОГНОРМОБР
LOGNORMDIST = ЛОГНОРМРАСП
MODE = МОДА
NEGBINOMDIST = ОТРБИНОМРАСП
NORMDIST = НОРМРАСП
NORMINV = НОРМОБР
NORMSDIST = НОРМСТРАСП
NORMSINV = НОРМСТОБР
PERCENTILE = ПЕРСЕНТИЛЬ
PERCENTRANK = ПРОЦЕНТРАНГ
POISSON = ПУАССОН
QUARTILE = КВАРТИЛЬ
RANK = РАНГ
STDEV = СТАНДОТКЛОН
STDEVP = СТАНДОТКЛОНП
TDIST = СТЬЮДРАСП
TINV = СТЬЮДРАСПОБР
TTEST = ТТЕСТ
VAR = ДИСП
VARP = ДИСПР
WEIBULL = ВЕЙБУЛЛ
ZTEST = ZТЕСТ
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/config 0000644 00000000650 15167673465 0020573 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## български (Bulgarian)
##
############################################################
ArgumentSeparator = ;
##
## (For future use)
##
currencySymbol = лв
##
## Error Codes
##
NULL = #ПРАЗНО!
DIV0 = #ДЕЛ/0!
VALUE = #СТОЙНОСТ!
REF = #РЕФ!
NAME = #ИМЕ?
NUM = #ЧИСЛО!
NA = #Н/Д
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions 0000644 00000022455 15167673465 0021362 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Ceština (Czech)
##
############################################################
##
## Funkce pro práci s datovými krychlemi (Cube Functions)
##
CUBEKPIMEMBER = CUBEKPIMEMBER
CUBEMEMBER = CUBEMEMBER
CUBEMEMBERPROPERTY = CUBEMEMBERPROPERTY
CUBERANKEDMEMBER = CUBERANKEDMEMBER
CUBESET = CUBESET
CUBESETCOUNT = CUBESETCOUNT
CUBEVALUE = CUBEVALUE
##
## Funkce databáze (Database Functions)
##
DAVERAGE = DPRŮMĚR
DCOUNT = DPOČET
DCOUNTA = DPOČET2
DGET = DZÍSKAT
DMAX = DMAX
DMIN = DMIN
DPRODUCT = DSOUČIN
DSTDEV = DSMODCH.VÝBĚR
DSTDEVP = DSMODCH
DSUM = DSUMA
DVAR = DVAR.VÝBĚR
DVARP = DVAR
##
## Funkce data a času (Date & Time Functions)
##
DATE = DATUM
DATEVALUE = DATUMHODN
DAY = DEN
DAYS = DAYS
DAYS360 = ROK360
EDATE = EDATE
EOMONTH = EOMONTH
HOUR = HODINA
ISOWEEKNUM = ISOWEEKNUM
MINUTE = MINUTA
MONTH = MĚSÍC
NETWORKDAYS = NETWORKDAYS
NETWORKDAYS.INTL = NETWORKDAYS.INTL
NOW = NYNÍ
SECOND = SEKUNDA
TIME = ČAS
TIMEVALUE = ČASHODN
TODAY = DNES
WEEKDAY = DENTÝDNE
WEEKNUM = WEEKNUM
WORKDAY = WORKDAY
WORKDAY.INTL = WORKDAY.INTL
YEAR = ROK
YEARFRAC = YEARFRAC
##
## Inženýrské funkce (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BIN2DEC
BIN2HEX = BIN2HEX
BIN2OCT = BIN2OCT
BITAND = BITAND
BITLSHIFT = BITLSHIFT
BITOR = BITOR
BITRSHIFT = BITRSHIFT
BITXOR = BITXOR
COMPLEX = COMPLEX
CONVERT = CONVERT
DEC2BIN = DEC2BIN
DEC2HEX = DEC2HEX
DEC2OCT = DEC2OCT
DELTA = DELTA
ERF = ERF
ERF.PRECISE = ERF.PRECISE
ERFC = ERFC
ERFC.PRECISE = ERFC.PRECISE
GESTEP = GESTEP
HEX2BIN = HEX2BIN
HEX2DEC = HEX2DEC
HEX2OCT = HEX2OCT
IMABS = IMABS
IMAGINARY = IMAGINARY
IMARGUMENT = IMARGUMENT
IMCONJUGATE = IMCONJUGATE
IMCOS = IMCOS
IMCOSH = IMCOSH
IMCOT = IMCOT
IMCSC = IMCSC
IMCSCH = IMCSCH
IMDIV = IMDIV
IMEXP = IMEXP
IMLN = IMLN
IMLOG10 = IMLOG10
IMLOG2 = IMLOG2
IMPOWER = IMPOWER
IMPRODUCT = IMPRODUCT
IMREAL = IMREAL
IMSEC = IMSEC
IMSECH = IMSECH
IMSIN = IMSIN
IMSINH = IMSINH
IMSQRT = IMSQRT
IMSUB = IMSUB
IMSUM = IMSUM
IMTAN = IMTAN
OCT2BIN = OCT2BIN
OCT2DEC = OCT2DEC
OCT2HEX = OCT2HEX
##
## Finanční funkce (Financial Functions)
##
ACCRINT = ACCRINT
ACCRINTM = ACCRINTM
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = COUPDAYBS
COUPDAYS = COUPDAYS
COUPDAYSNC = COUPDAYSNC
COUPNCD = COUPNCD
COUPNUM = COUPNUM
COUPPCD = COUPPCD
CUMIPMT = CUMIPMT
CUMPRINC = CUMPRINC
DB = ODPIS.ZRYCH
DDB = ODPIS.ZRYCH2
DISC = DISC
DOLLARDE = DOLLARDE
DOLLARFR = DOLLARFR
DURATION = DURATION
EFFECT = EFFECT
FV = BUDHODNOTA
FVSCHEDULE = FVSCHEDULE
INTRATE = INTRATE
IPMT = PLATBA.ÚROK
IRR = MÍRA.VÝNOSNOSTI
ISPMT = ISPMT
MDURATION = MDURATION
MIRR = MOD.MÍRA.VÝNOSNOSTI
NOMINAL = NOMINAL
NPER = POČET.OBDOBÍ
NPV = ČISTÁ.SOUČHODNOTA
ODDFPRICE = ODDFPRICE
ODDFYIELD = ODDFYIELD
ODDLPRICE = ODDLPRICE
ODDLYIELD = ODDLYIELD
PDURATION = PDURATION
PMT = PLATBA
PPMT = PLATBA.ZÁKLAD
PRICE = PRICE
PRICEDISC = PRICEDISC
PRICEMAT = PRICEMAT
PV = SOUČHODNOTA
RATE = ÚROKOVÁ.MÍRA
RECEIVED = RECEIVED
RRI = RRI
SLN = ODPIS.LIN
SYD = ODPIS.NELIN
TBILLEQ = TBILLEQ
TBILLPRICE = TBILLPRICE
TBILLYIELD = TBILLYIELD
VDB = ODPIS.ZA.INT
XIRR = XIRR
XNPV = XNPV
YIELD = YIELD
YIELDDISC = YIELDDISC
YIELDMAT = YIELDMAT
##
## Informační funkce (Information Functions)
##
CELL = POLÍČKO
ERROR.TYPE = CHYBA.TYP
INFO = O.PROSTŘEDÍ
ISBLANK = JE.PRÁZDNÉ
ISERR = JE.CHYBA
ISERROR = JE.CHYBHODN
ISEVEN = ISEVEN
ISFORMULA = ISFORMULA
ISLOGICAL = JE.LOGHODN
ISNA = JE.NEDEF
ISNONTEXT = JE.NETEXT
ISNUMBER = JE.ČISLO
ISODD = ISODD
ISREF = JE.ODKAZ
ISTEXT = JE.TEXT
N = N
NA = NEDEF
SHEET = SHEET
SHEETS = SHEETS
TYPE = TYP
##
## Logické funkce (Logical Functions)
##
AND = A
FALSE = NEPRAVDA
IF = KDYŽ
IFERROR = IFERROR
IFNA = IFNA
IFS = IFS
NOT = NE
OR = NEBO
SWITCH = SWITCH
TRUE = PRAVDA
XOR = XOR
##
## Vyhledávací funkce a funkce pro odkazy (Lookup & Reference Functions)
##
ADDRESS = ODKAZ
AREAS = POČET.BLOKŮ
CHOOSE = ZVOLIT
COLUMN = SLOUPEC
COLUMNS = SLOUPCE
FORMULATEXT = FORMULATEXT
GETPIVOTDATA = ZÍSKATKONTDATA
HLOOKUP = VVYHLEDAT
HYPERLINK = HYPERTEXTOVÝ.ODKAZ
INDEX = INDEX
INDIRECT = NEPŘÍMÝ.ODKAZ
LOOKUP = VYHLEDAT
MATCH = POZVYHLEDAT
OFFSET = POSUN
ROW = ŘÁDEK
ROWS = ŘÁDKY
RTD = RTD
TRANSPOSE = TRANSPOZICE
VLOOKUP = SVYHLEDAT
##
## Matematické a trigonometrické funkce (Math & Trig Functions)
##
ABS = ABS
ACOS = ARCCOS
ACOSH = ARCCOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = AGGREGATE
ARABIC = ARABIC
ASIN = ARCSIN
ASINH = ARCSINH
ATAN = ARCTG
ATAN2 = ARCTG2
ATANH = ARCTGH
BASE = BASE
CEILING.MATH = CEILING.MATH
COMBIN = KOMBINACE
COMBINA = COMBINA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DECIMAL
DEGREES = DEGREES
EVEN = ZAOKROUHLIT.NA.SUDÉ
EXP = EXP
FACT = FAKTORIÁL
FACTDOUBLE = FACTDOUBLE
FLOOR.MATH = FLOOR.MATH
GCD = GCD
INT = CELÁ.ČÁST
LCM = LCM
LN = LN
LOG = LOGZ
LOG10 = LOG
MDETERM = DETERMINANT
MINVERSE = INVERZE
MMULT = SOUČIN.MATIC
MOD = MOD
MROUND = MROUND
MULTINOMIAL = MULTINOMIAL
MUNIT = MUNIT
ODD = ZAOKROUHLIT.NA.LICHÉ
PI = PI
POWER = POWER
PRODUCT = SOUČIN
QUOTIENT = QUOTIENT
RADIANS = RADIANS
RAND = NÁHČÍSLO
RANDBETWEEN = RANDBETWEEN
ROMAN = ROMAN
ROUND = ZAOKROUHLIT
ROUNDDOWN = ROUNDDOWN
ROUNDUP = ROUNDUP
SEC = SEC
SECH = SECH
SERIESSUM = SERIESSUM
SIGN = SIGN
SIN = SIN
SINH = SINH
SQRT = ODMOCNINA
SQRTPI = SQRTPI
SUBTOTAL = SUBTOTAL
SUM = SUMA
SUMIF = SUMIF
SUMIFS = SUMIFS
SUMPRODUCT = SOUČIN.SKALÁRNÍ
SUMSQ = SUMA.ČTVERCŮ
SUMX2MY2 = SUMX2MY2
SUMX2PY2 = SUMX2PY2
SUMXMY2 = SUMXMY2
TAN = TG
TANH = TGH
TRUNC = USEKNOUT
##
## Statistické funkce (Statistical Functions)
##
AVEDEV = PRŮMODCHYLKA
AVERAGE = PRŮMĚR
AVERAGEA = AVERAGEA
AVERAGEIF = AVERAGEIF
AVERAGEIFS = AVERAGEIFS
BETA.DIST = BETA.DIST
BETA.INV = BETA.INV
BINOM.DIST = BINOM.DIST
BINOM.DIST.RANGE = BINOM.DIST.RANGE
BINOM.INV = BINOM.INV
CHISQ.DIST = CHISQ.DIST
CHISQ.DIST.RT = CHISQ.DIST.RT
CHISQ.INV = CHISQ.INV
CHISQ.INV.RT = CHISQ.INV.RT
CHISQ.TEST = CHISQ.TEST
CONFIDENCE.NORM = CONFIDENCE.NORM
CONFIDENCE.T = CONFIDENCE.T
CORREL = CORREL
COUNT = POČET
COUNTA = POČET2
COUNTBLANK = COUNTBLANK
COUNTIF = COUNTIF
COUNTIFS = COUNTIFS
COVARIANCE.P = COVARIANCE.P
COVARIANCE.S = COVARIANCE.S
DEVSQ = DEVSQ
EXPON.DIST = EXPON.DIST
F.DIST = F.DIST
F.DIST.RT = F.DIST.RT
F.INV = F.INV
F.INV.RT = F.INV.RT
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = FORECAST.ETS
FORECAST.ETS.CONFINT = FORECAST.ETS.CONFINT
FORECAST.ETS.SEASONALITY = FORECAST.ETS.SEASONALITY
FORECAST.ETS.STAT = FORECAST.ETS.STAT
FORECAST.LINEAR = FORECAST.LINEAR
FREQUENCY = ČETNOSTI
GAMMA = GAMMA
GAMMA.DIST = GAMMA.DIST
GAMMA.INV = GAMMA.INV
GAMMALN = GAMMALN
GAMMALN.PRECISE = GAMMALN.PRECISE
GAUSS = GAUSS
GEOMEAN = GEOMEAN
GROWTH = LOGLINTREND
HARMEAN = HARMEAN
HYPGEOM.DIST = HYPGEOM.DIST
INTERCEPT = INTERCEPT
KURT = KURT
LARGE = LARGE
LINEST = LINREGRESE
LOGEST = LOGLINREGRESE
LOGNORM.DIST = LOGNORM.DIST
LOGNORM.INV = LOGNORM.INV
MAX = MAX
MAXA = MAXA
MAXIFS = MAXIFS
MEDIAN = MEDIAN
MIN = MIN
MINA = MINA
MINIFS = MINIFS
MODE.MULT = MODE.MULT
MODE.SNGL = MODE.SNGL
NEGBINOM.DIST = NEGBINOM.DIST
NORM.DIST = NORM.DIST
NORM.INV = NORM.INV
NORM.S.DIST = NORM.S.DIST
NORM.S.INV = NORM.S.INV
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTIL.EXC
PERCENTILE.INC = PERCENTIL.INC
PERCENTRANK.EXC = PERCENTRANK.EXC
PERCENTRANK.INC = PERCENTRANK.INC
PERMUT = PERMUTACE
PERMUTATIONA = PERMUTATIONA
PHI = PHI
POISSON.DIST = POISSON.DIST
PROB = PROB
QUARTILE.EXC = QUARTIL.EXC
QUARTILE.INC = QUARTIL.INC
RANK.AVG = RANK.AVG
RANK.EQ = RANK.EQ
RSQ = RKQ
SKEW = SKEW
SKEW.P = SKEW.P
SLOPE = SLOPE
SMALL = SMALL
STANDARDIZE = STANDARDIZE
STDEV.P = SMODCH.P
STDEV.S = SMODCH.VÝBĚR.S
STDEVA = STDEVA
STDEVPA = STDEVPA
STEYX = STEYX
T.DIST = T.DIST
T.DIST.2T = T.DIST.2T
T.DIST.RT = T.DIST.RT
T.INV = T.INV
T.INV.2T = T.INV.2T
T.TEST = T.TEST
TREND = LINTREND
TRIMMEAN = TRIMMEAN
VAR.P = VAR.P
VAR.S = VAR.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = WEIBULL.DIST
Z.TEST = Z.TEST
##
## Textové funkce (Text Functions)
##
BAHTTEXT = BAHTTEXT
CHAR = ZNAK
CLEAN = VYČISTIT
CODE = KÓD
CONCAT = CONCAT
DOLLAR = KČ
EXACT = STEJNÉ
FIND = NAJÍT
FIXED = ZAOKROUHLIT.NA.TEXT
LEFT = ZLEVA
LEN = DÉLKA
LOWER = MALÁ
MID = ČÁST
NUMBERVALUE = NUMBERVALUE
PHONETIC = ZVUKOVÉ
PROPER = VELKÁ2
REPLACE = NAHRADIT
REPT = OPAKOVAT
RIGHT = ZPRAVA
SEARCH = HLEDAT
SUBSTITUTE = DOSADIT
T = T
TEXT = HODNOTA.NA.TEXT
TEXTJOIN = TEXTJOIN
TRIM = PROČISTIT
UNICHAR = UNICHAR
UNICODE = UNICODE
UPPER = VELKÁ
VALUE = HODNOTA
##
## Webové funkce (Web Functions)
##
ENCODEURL = ENCODEURL
FILTERXML = FILTERXML
WEBSERVICE = WEBSERVICE
##
## Funkce pro kompatibilitu (Compatibility Functions)
##
BETADIST = BETADIST
BETAINV = BETAINV
BINOMDIST = BINOMDIST
CEILING = ZAOKR.NAHORU
CHIDIST = CHIDIST
CHIINV = CHIINV
CHITEST = CHITEST
CONCATENATE = CONCATENATE
CONFIDENCE = CONFIDENCE
COVAR = COVAR
CRITBINOM = CRITBINOM
EXPONDIST = EXPONDIST
FDIST = FDIST
FINV = FINV
FLOOR = ZAOKR.DOLŮ
FORECAST = FORECAST
FTEST = FTEST
GAMMADIST = GAMMADIST
GAMMAINV = GAMMAINV
HYPGEOMDIST = HYPGEOMDIST
LOGINV = LOGINV
LOGNORMDIST = LOGNORMDIST
MODE = MODE
NEGBINOMDIST = NEGBINOMDIST
NORMDIST = NORMDIST
NORMINV = NORMINV
NORMSDIST = NORMSDIST
NORMSINV = NORMSINV
PERCENTILE = PERCENTIL
PERCENTRANK = PERCENTRANK
POISSON = POISSON
QUARTILE = QUARTIL
RANK = RANK
STDEV = SMODCH.VÝBĚR
STDEVP = SMODCH
TDIST = TDIST
TINV = TINV
TTEST = TTEST
VAR = VAR.VÝBĚR
VARP = VAR
WEIBULL = WEIBULL
ZTEST = ZTEST
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config 0000644 00000000535 15167673465 0020612 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Ceština (Czech)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL
DIV0 = #DĚLENÍ_NULOU!
VALUE = #HODNOTA!
REF = #ODKAZ!
NAME = #NÁZEV?
NUM = #ČÍSLO!
NA = #NENÍ_K_DISPOZICI
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions 0000644 00000033315 15167673465 0021400 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## русский язык (Russian)
##
############################################################
##
## Функции кубов (Cube Functions)
##
CUBEKPIMEMBER = КУБЭЛЕМЕНТКИП
CUBEMEMBER = КУБЭЛЕМЕНТ
CUBEMEMBERPROPERTY = КУБСВОЙСТВОЭЛЕМЕНТА
CUBERANKEDMEMBER = КУБПОРЭЛЕМЕНТ
CUBESET = КУБМНОЖ
CUBESETCOUNT = КУБЧИСЛОЭЛМНОЖ
CUBEVALUE = КУБЗНАЧЕНИЕ
##
## Функции для работы с базами данных (Database Functions)
##
DAVERAGE = ДСРЗНАЧ
DCOUNT = БСЧЁТ
DCOUNTA = БСЧЁТА
DGET = БИЗВЛЕЧЬ
DMAX = ДМАКС
DMIN = ДМИН
DPRODUCT = БДПРОИЗВЕД
DSTDEV = ДСТАНДОТКЛ
DSTDEVP = ДСТАНДОТКЛП
DSUM = БДСУММ
DVAR = БДДИСП
DVARP = БДДИСПП
##
## Функции даты и времени (Date & Time Functions)
##
DATE = ДАТА
DATEDIF = РАЗНДАТ
DATESTRING = СТРОКАДАННЫХ
DATEVALUE = ДАТАЗНАЧ
DAY = ДЕНЬ
DAYS = ДНИ
DAYS360 = ДНЕЙ360
EDATE = ДАТАМЕС
EOMONTH = КОНМЕСЯЦА
HOUR = ЧАС
ISOWEEKNUM = НОМНЕДЕЛИ.ISO
MINUTE = МИНУТЫ
MONTH = МЕСЯЦ
NETWORKDAYS = ЧИСТРАБДНИ
NETWORKDAYS.INTL = ЧИСТРАБДНИ.МЕЖД
NOW = ТДАТА
SECOND = СЕКУНДЫ
THAIDAYOFWEEK = ТАЙДЕНЬНЕД
THAIMONTHOFYEAR = ТАЙМЕСЯЦ
THAIYEAR = ТАЙГОД
TIME = ВРЕМЯ
TIMEVALUE = ВРЕМЗНАЧ
TODAY = СЕГОДНЯ
WEEKDAY = ДЕНЬНЕД
WEEKNUM = НОМНЕДЕЛИ
WORKDAY = РАБДЕНЬ
WORKDAY.INTL = РАБДЕНЬ.МЕЖД
YEAR = ГОД
YEARFRAC = ДОЛЯГОДА
##
## Инженерные функции (Engineering Functions)
##
BESSELI = БЕССЕЛЬ.I
BESSELJ = БЕССЕЛЬ.J
BESSELK = БЕССЕЛЬ.K
BESSELY = БЕССЕЛЬ.Y
BIN2DEC = ДВ.В.ДЕС
BIN2HEX = ДВ.В.ШЕСТН
BIN2OCT = ДВ.В.ВОСЬМ
BITAND = БИТ.И
BITLSHIFT = БИТ.СДВИГЛ
BITOR = БИТ.ИЛИ
BITRSHIFT = БИТ.СДВИГП
BITXOR = БИТ.ИСКЛИЛИ
COMPLEX = КОМПЛЕКСН
CONVERT = ПРЕОБР
DEC2BIN = ДЕС.В.ДВ
DEC2HEX = ДЕС.В.ШЕСТН
DEC2OCT = ДЕС.В.ВОСЬМ
DELTA = ДЕЛЬТА
ERF = ФОШ
ERF.PRECISE = ФОШ.ТОЧН
ERFC = ДФОШ
ERFC.PRECISE = ДФОШ.ТОЧН
GESTEP = ПОРОГ
HEX2BIN = ШЕСТН.В.ДВ
HEX2DEC = ШЕСТН.В.ДЕС
HEX2OCT = ШЕСТН.В.ВОСЬМ
IMABS = МНИМ.ABS
IMAGINARY = МНИМ.ЧАСТЬ
IMARGUMENT = МНИМ.АРГУМЕНТ
IMCONJUGATE = МНИМ.СОПРЯЖ
IMCOS = МНИМ.COS
IMCOSH = МНИМ.COSH
IMCOT = МНИМ.COT
IMCSC = МНИМ.CSC
IMCSCH = МНИМ.CSCH
IMDIV = МНИМ.ДЕЛ
IMEXP = МНИМ.EXP
IMLN = МНИМ.LN
IMLOG10 = МНИМ.LOG10
IMLOG2 = МНИМ.LOG2
IMPOWER = МНИМ.СТЕПЕНЬ
IMPRODUCT = МНИМ.ПРОИЗВЕД
IMREAL = МНИМ.ВЕЩ
IMSEC = МНИМ.SEC
IMSECH = МНИМ.SECH
IMSIN = МНИМ.SIN
IMSINH = МНИМ.SINH
IMSQRT = МНИМ.КОРЕНЬ
IMSUB = МНИМ.РАЗН
IMSUM = МНИМ.СУММ
IMTAN = МНИМ.TAN
OCT2BIN = ВОСЬМ.В.ДВ
OCT2DEC = ВОСЬМ.В.ДЕС
OCT2HEX = ВОСЬМ.В.ШЕСТН
##
## Финансовые функции (Financial Functions)
##
ACCRINT = НАКОПДОХОД
ACCRINTM = НАКОПДОХОДПОГАШ
AMORDEGRC = АМОРУМ
AMORLINC = АМОРУВ
COUPDAYBS = ДНЕЙКУПОНДО
COUPDAYS = ДНЕЙКУПОН
COUPDAYSNC = ДНЕЙКУПОНПОСЛЕ
COUPNCD = ДАТАКУПОНПОСЛЕ
COUPNUM = ЧИСЛКУПОН
COUPPCD = ДАТАКУПОНДО
CUMIPMT = ОБЩПЛАТ
CUMPRINC = ОБЩДОХОД
DB = ФУО
DDB = ДДОБ
DISC = СКИДКА
DOLLARDE = РУБЛЬ.ДЕС
DOLLARFR = РУБЛЬ.ДРОБЬ
DURATION = ДЛИТ
EFFECT = ЭФФЕКТ
FV = БС
FVSCHEDULE = БЗРАСПИС
INTRATE = ИНОРМА
IPMT = ПРПЛТ
IRR = ВСД
ISPMT = ПРОЦПЛАТ
MDURATION = МДЛИТ
MIRR = МВСД
NOMINAL = НОМИНАЛ
NPER = КПЕР
NPV = ЧПС
ODDFPRICE = ЦЕНАПЕРВНЕРЕГ
ODDFYIELD = ДОХОДПЕРВНЕРЕГ
ODDLPRICE = ЦЕНАПОСЛНЕРЕГ
ODDLYIELD = ДОХОДПОСЛНЕРЕГ
PDURATION = ПДЛИТ
PMT = ПЛТ
PPMT = ОСПЛТ
PRICE = ЦЕНА
PRICEDISC = ЦЕНАСКИДКА
PRICEMAT = ЦЕНАПОГАШ
PV = ПС
RATE = СТАВКА
RECEIVED = ПОЛУЧЕНО
RRI = ЭКВ.СТАВКА
SLN = АПЛ
SYD = АСЧ
TBILLEQ = РАВНОКЧЕК
TBILLPRICE = ЦЕНАКЧЕК
TBILLYIELD = ДОХОДКЧЕК
USDOLLAR = ДОЛЛСША
VDB = ПУО
XIRR = ЧИСТВНДОХ
XNPV = ЧИСТНЗ
YIELD = ДОХОД
YIELDDISC = ДОХОДСКИДКА
YIELDMAT = ДОХОДПОГАШ
##
## Информационные функции (Information Functions)
##
CELL = ЯЧЕЙКА
ERROR.TYPE = ТИП.ОШИБКИ
INFO = ИНФОРМ
ISBLANK = ЕПУСТО
ISERR = ЕОШ
ISERROR = ЕОШИБКА
ISEVEN = ЕЧЁТН
ISFORMULA = ЕФОРМУЛА
ISLOGICAL = ЕЛОГИЧ
ISNA = ЕНД
ISNONTEXT = ЕНЕТЕКСТ
ISNUMBER = ЕЧИСЛО
ISODD = ЕНЕЧЁТ
ISREF = ЕССЫЛКА
ISTEXT = ЕТЕКСТ
N = Ч
NA = НД
SHEET = ЛИСТ
SHEETS = ЛИСТЫ
TYPE = ТИП
##
## Логические функции (Logical Functions)
##
AND = И
FALSE = ЛОЖЬ
IF = ЕСЛИ
IFERROR = ЕСЛИОШИБКА
IFNA = ЕСНД
IFS = УСЛОВИЯ
NOT = НЕ
OR = ИЛИ
SWITCH = ПЕРЕКЛЮЧ
TRUE = ИСТИНА
XOR = ИСКЛИЛИ
##
## Функции ссылки и поиска (Lookup & Reference Functions)
##
ADDRESS = АДРЕС
AREAS = ОБЛАСТИ
CHOOSE = ВЫБОР
COLUMN = СТОЛБЕЦ
COLUMNS = ЧИСЛСТОЛБ
FILTER = ФИЛЬТР
FORMULATEXT = Ф.ТЕКСТ
GETPIVOTDATA = ПОЛУЧИТЬ.ДАННЫЕ.СВОДНОЙ.ТАБЛИЦЫ
HLOOKUP = ГПР
HYPERLINK = ГИПЕРССЫЛКА
INDEX = ИНДЕКС
INDIRECT = ДВССЫЛ
LOOKUP = ПРОСМОТР
MATCH = ПОИСКПОЗ
OFFSET = СМЕЩ
ROW = СТРОКА
ROWS = ЧСТРОК
RTD = ДРВ
SORT = СОРТ
SORTBY = СОРТПО
TRANSPOSE = ТРАНСП
UNIQUE = УНИК
VLOOKUP = ВПР
XLOOKUP = ПРОСМОТРX
XMATCH = ПОИСКПОЗX
##
## Математические и тригонометрические функции (Math & Trig Functions)
##
ABS = ABS
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = АГРЕГАТ
ARABIC = АРАБСКОЕ
ASIN = ASIN
ASINH = ASINH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = ОСНОВАНИЕ
CEILING.MATH = ОКРВВЕРХ.МАТ
CEILING.PRECISE = ОКРВВЕРХ.ТОЧН
COMBIN = ЧИСЛКОМБ
COMBINA = ЧИСЛКОМБА
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = ДЕС
DEGREES = ГРАДУСЫ
ECMA.CEILING = ECMA.ОКРВВЕРХ
EVEN = ЧЁТН
EXP = EXP
FACT = ФАКТР
FACTDOUBLE = ДВФАКТР
FLOOR.MATH = ОКРВНИЗ.МАТ
FLOOR.PRECISE = ОКРВНИЗ.ТОЧН
GCD = НОД
INT = ЦЕЛОЕ
ISO.CEILING = ISO.ОКРВВЕРХ
LCM = НОК
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = МОПРЕД
MINVERSE = МОБР
MMULT = МУМНОЖ
MOD = ОСТАТ
MROUND = ОКРУГЛТ
MULTINOMIAL = МУЛЬТИНОМ
MUNIT = МЕДИН
ODD = НЕЧЁТ
PI = ПИ
POWER = СТЕПЕНЬ
PRODUCT = ПРОИЗВЕД
QUOTIENT = ЧАСТНОЕ
RADIANS = РАДИАНЫ
RAND = СЛЧИС
RANDARRAY = СЛУЧМАССИВ
RANDBETWEEN = СЛУЧМЕЖДУ
ROMAN = РИМСКОЕ
ROUND = ОКРУГЛ
ROUNDBAHTDOWN = ОКРУГЛБАТВНИЗ
ROUNDBAHTUP = ОКРУГЛБАТВВЕРХ
ROUNDDOWN = ОКРУГЛВНИЗ
ROUNDUP = ОКРУГЛВВЕРХ
SEC = SEC
SECH = SECH
SERIESSUM = РЯД.СУММ
SEQUENCE = ПОСЛЕДОВ
SIGN = ЗНАК
SIN = SIN
SINH = SINH
SQRT = КОРЕНЬ
SQRTPI = КОРЕНЬПИ
SUBTOTAL = ПРОМЕЖУТОЧНЫЕ.ИТОГИ
SUM = СУММ
SUMIF = СУММЕСЛИ
SUMIFS = СУММЕСЛИМН
SUMPRODUCT = СУММПРОИЗВ
SUMSQ = СУММКВ
SUMX2MY2 = СУММРАЗНКВ
SUMX2PY2 = СУММСУММКВ
SUMXMY2 = СУММКВРАЗН
TAN = TAN
TANH = TANH
TRUNC = ОТБР
##
## Статистические функции (Statistical Functions)
##
AVEDEV = СРОТКЛ
AVERAGE = СРЗНАЧ
AVERAGEA = СРЗНАЧА
AVERAGEIF = СРЗНАЧЕСЛИ
AVERAGEIFS = СРЗНАЧЕСЛИМН
BETA.DIST = БЕТА.РАСП
BETA.INV = БЕТА.ОБР
BINOM.DIST = БИНОМ.РАСП
BINOM.DIST.RANGE = БИНОМ.РАСП.ДИАП
BINOM.INV = БИНОМ.ОБР
CHISQ.DIST = ХИ2.РАСП
CHISQ.DIST.RT = ХИ2.РАСП.ПХ
CHISQ.INV = ХИ2.ОБР
CHISQ.INV.RT = ХИ2.ОБР.ПХ
CHISQ.TEST = ХИ2.ТЕСТ
CONFIDENCE.NORM = ДОВЕРИТ.НОРМ
CONFIDENCE.T = ДОВЕРИТ.СТЬЮДЕНТ
CORREL = КОРРЕЛ
COUNT = СЧЁТ
COUNTA = СЧЁТЗ
COUNTBLANK = СЧИТАТЬПУСТОТЫ
COUNTIF = СЧЁТЕСЛИ
COUNTIFS = СЧЁТЕСЛИМН
COVARIANCE.P = КОВАРИАЦИЯ.Г
COVARIANCE.S = КОВАРИАЦИЯ.В
DEVSQ = КВАДРОТКЛ
EXPON.DIST = ЭКСП.РАСП
F.DIST = F.РАСП
F.DIST.RT = F.РАСП.ПХ
F.INV = F.ОБР
F.INV.RT = F.ОБР.ПХ
F.TEST = F.ТЕСТ
FISHER = ФИШЕР
FISHERINV = ФИШЕРОБР
FORECAST.ETS = ПРЕДСКАЗ.ETS
FORECAST.ETS.CONFINT = ПРЕДСКАЗ.ЕTS.ДОВИНТЕРВАЛ
FORECAST.ETS.SEASONALITY = ПРЕДСКАЗ.ETS.СЕЗОННОСТЬ
FORECAST.ETS.STAT = ПРЕДСКАЗ.ETS.СТАТ
FORECAST.LINEAR = ПРЕДСКАЗ.ЛИНЕЙН
FREQUENCY = ЧАСТОТА
GAMMA = ГАММА
GAMMA.DIST = ГАММА.РАСП
GAMMA.INV = ГАММА.ОБР
GAMMALN = ГАММАНЛОГ
GAMMALN.PRECISE = ГАММАНЛОГ.ТОЧН
GAUSS = ГАУСС
GEOMEAN = СРГЕОМ
GROWTH = РОСТ
HARMEAN = СРГАРМ
HYPGEOM.DIST = ГИПЕРГЕОМ.РАСП
INTERCEPT = ОТРЕЗОК
KURT = ЭКСЦЕСС
LARGE = НАИБОЛЬШИЙ
LINEST = ЛИНЕЙН
LOGEST = ЛГРФПРИБЛ
LOGNORM.DIST = ЛОГНОРМ.РАСП
LOGNORM.INV = ЛОГНОРМ.ОБР
MAX = МАКС
MAXA = МАКСА
MAXIFS = МАКСЕСЛИ
MEDIAN = МЕДИАНА
MIN = МИН
MINA = МИНА
MINIFS = МИНЕСЛИ
MODE.MULT = МОДА.НСК
MODE.SNGL = МОДА.ОДН
NEGBINOM.DIST = ОТРБИНОМ.РАСП
NORM.DIST = НОРМ.РАСП
NORM.INV = НОРМ.ОБР
NORM.S.DIST = НОРМ.СТ.РАСП
NORM.S.INV = НОРМ.СТ.ОБР
PEARSON = PEARSON
PERCENTILE.EXC = ПРОЦЕНТИЛЬ.ИСКЛ
PERCENTILE.INC = ПРОЦЕНТИЛЬ.ВКЛ
PERCENTRANK.EXC = ПРОЦЕНТРАНГ.ИСКЛ
PERCENTRANK.INC = ПРОЦЕНТРАНГ.ВКЛ
PERMUT = ПЕРЕСТ
PERMUTATIONA = ПЕРЕСТА
PHI = ФИ
POISSON.DIST = ПУАССОН.РАСП
PROB = ВЕРОЯТНОСТЬ
QUARTILE.EXC = КВАРТИЛЬ.ИСКЛ
QUARTILE.INC = КВАРТИЛЬ.ВКЛ
RANK.AVG = РАНГ.СР
RANK.EQ = РАНГ.РВ
RSQ = КВПИРСОН
SKEW = СКОС
SKEW.P = СКОС.Г
SLOPE = НАКЛОН
SMALL = НАИМЕНЬШИЙ
STANDARDIZE = НОРМАЛИЗАЦИЯ
STDEV.P = СТАНДОТКЛОН.Г
STDEV.S = СТАНДОТКЛОН.В
STDEVA = СТАНДОТКЛОНА
STDEVPA = СТАНДОТКЛОНПА
STEYX = СТОШYX
T.DIST = СТЬЮДЕНТ.РАСП
T.DIST.2T = СТЬЮДЕНТ.РАСП.2Х
T.DIST.RT = СТЬЮДЕНТ.РАСП.ПХ
T.INV = СТЬЮДЕНТ.ОБР
T.INV.2T = СТЬЮДЕНТ.ОБР.2Х
T.TEST = СТЬЮДЕНТ.ТЕСТ
TREND = ТЕНДЕНЦИЯ
TRIMMEAN = УРЕЗСРЕДНЕЕ
VAR.P = ДИСП.Г
VAR.S = ДИСП.В
VARA = ДИСПА
VARPA = ДИСПРА
WEIBULL.DIST = ВЕЙБУЛЛ.РАСП
Z.TEST = Z.ТЕСТ
##
## Текстовые функции (Text Functions)
##
ARRAYTOTEXT = МАССИВВТЕКСТ
BAHTTEXT = БАТТЕКСТ
CHAR = СИМВОЛ
CLEAN = ПЕЧСИМВ
CODE = КОДСИМВ
CONCAT = СЦЕП
DBCS = БДЦС
DOLLAR = РУБЛЬ
EXACT = СОВПАД
FIND = НАЙТИ
FINDB = НАЙТИБ
FIXED = ФИКСИРОВАННЫЙ
ISTHAIDIGIT = ЕТАЙЦИФРЫ
LEFT = ЛЕВСИМВ
LEFTB = ЛЕВБ
LEN = ДЛСТР
LENB = ДЛИНБ
LOWER = СТРОЧН
MID = ПСТР
MIDB = ПСТРБ
NUMBERSTRING = СТРОКАЧИСЕЛ
NUMBERVALUE = ЧЗНАЧ
PROPER = ПРОПНАЧ
REPLACE = ЗАМЕНИТЬ
REPLACEB = ЗАМЕНИТЬБ
REPT = ПОВТОР
RIGHT = ПРАВСИМВ
RIGHTB = ПРАВБ
SEARCH = ПОИСК
SEARCHB = ПОИСКБ
SUBSTITUTE = ПОДСТАВИТЬ
T = Т
TEXT = ТЕКСТ
TEXTJOIN = ОБЪЕДИНИТЬ
THAIDIGIT = ТАЙЦИФРА
THAINUMSOUND = ТАЙЧИСЛОВЗВУК
THAINUMSTRING = ТАЙЧИСЛОВСТРОКУ
THAISTRINGLENGTH = ТАЙДЛИНАСТРОКИ
TRIM = СЖПРОБЕЛЫ
UNICHAR = ЮНИСИМВ
UNICODE = UNICODE
UPPER = ПРОПИСН
VALUE = ЗНАЧЕН
VALUETOTEXT = ЗНАЧЕНИЕВТЕКСТ
##
## Веб-функции (Web Functions)
##
ENCODEURL = КОДИР.URL
FILTERXML = ФИЛЬТР.XML
WEBSERVICE = ВЕБСЛУЖБА
##
## Функции совместимости (Compatibility Functions)
##
BETADIST = БЕТАРАСП
BETAINV = БЕТАОБР
BINOMDIST = БИНОМРАСП
CEILING = ОКРВВЕРХ
CHIDIST = ХИ2РАСП
CHIINV = ХИ2ОБР
CHITEST = ХИ2ТЕСТ
CONCATENATE = СЦЕПИТЬ
CONFIDENCE = ДОВЕРИТ
COVAR = КОВАР
CRITBINOM = КРИТБИНОМ
EXPONDIST = ЭКСПРАСП
FDIST = FРАСП
FINV = FРАСПОБР
FLOOR = ОКРВНИЗ
FORECAST = ПРЕДСКАЗ
FTEST = ФТЕСТ
GAMMADIST = ГАММАРАСП
GAMMAINV = ГАММАОБР
HYPGEOMDIST = ГИПЕРГЕОМЕТ
LOGINV = ЛОГНОРМОБР
LOGNORMDIST = ЛОГНОРМРАСП
MODE = МОДА
NEGBINOMDIST = ОТРБИНОМРАСП
NORMDIST = НОРМРАСП
NORMINV = НОРМОБР
NORMSDIST = НОРМСТРАСП
NORMSINV = НОРМСТОБР
PERCENTILE = ПЕРСЕНТИЛЬ
PERCENTRANK = ПРОЦЕНТРАНГ
POISSON = ПУАССОН
QUARTILE = КВАРТИЛЬ
RANK = РАНГ
STDEV = СТАНДОТКЛОН
STDEVP = СТАНДОТКЛОНП
TDIST = СТЬЮДРАСП
TINV = СТЬЮДРАСПОБР
TTEST = ТТЕСТ
VAR = ДИСП
VARP = ДИСПР
WEIBULL = ВЕЙБУЛЛ
ZTEST = ZТЕСТ
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config 0000644 00000000566 15167673465 0020637 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## русский язык (Russian)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #ПУСТО!
DIV0 = #ДЕЛ/0!
VALUE = #ЗНАЧ!
REF = #ССЫЛКА!
NAME = #ИМЯ?
NUM = #ЧИСЛО!
NA = #Н/Д
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions 0000644 00000023241 15167673465 0021377 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Svenska (Swedish)
##
############################################################
##
## Kubfunktioner (Cube Functions)
##
CUBEKPIMEMBER = KUBKPIMEDLEM
CUBEMEMBER = KUBMEDLEM
CUBEMEMBERPROPERTY = KUBMEDLEMSEGENSKAP
CUBERANKEDMEMBER = KUBRANGORDNADMEDLEM
CUBESET = KUBUPPSÄTTNING
CUBESETCOUNT = KUBUPPSÄTTNINGANTAL
CUBEVALUE = KUBVÄRDE
##
## Databasfunktioner (Database Functions)
##
DAVERAGE = DMEDEL
DCOUNT = DANTAL
DCOUNTA = DANTALV
DGET = DHÄMTA
DMAX = DMAX
DMIN = DMIN
DPRODUCT = DPRODUKT
DSTDEV = DSTDAV
DSTDEVP = DSTDAVP
DSUM = DSUMMA
DVAR = DVARIANS
DVARP = DVARIANSP
##
## Tid- och datumfunktioner (Date & Time Functions)
##
DATE = DATUM
DATEVALUE = DATUMVÄRDE
DAY = DAG
DAYS = DAGAR
DAYS360 = DAGAR360
EDATE = EDATUM
EOMONTH = SLUTMÅNAD
HOUR = TIMME
ISOWEEKNUM = ISOVECKONR
MINUTE = MINUT
MONTH = MÅNAD
NETWORKDAYS = NETTOARBETSDAGAR
NETWORKDAYS.INTL = NETTOARBETSDAGAR.INT
NOW = NU
SECOND = SEKUND
THAIDAYOFWEEK = THAIVECKODAG
THAIMONTHOFYEAR = THAIMÅNAD
THAIYEAR = THAIÅR
TIME = KLOCKSLAG
TIMEVALUE = TIDVÄRDE
TODAY = IDAG
WEEKDAY = VECKODAG
WEEKNUM = VECKONR
WORKDAY = ARBETSDAGAR
WORKDAY.INTL = ARBETSDAGAR.INT
YEAR = ÅR
YEARFRAC = ÅRDEL
##
## Tekniska funktioner (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BIN.TILL.DEC
BIN2HEX = BIN.TILL.HEX
BIN2OCT = BIN.TILL.OKT
BITAND = BITOCH
BITLSHIFT = BITVSKIFT
BITOR = BITELLER
BITRSHIFT = BITHSKIFT
BITXOR = BITXELLER
COMPLEX = KOMPLEX
CONVERT = KONVERTERA
DEC2BIN = DEC.TILL.BIN
DEC2HEX = DEC.TILL.HEX
DEC2OCT = DEC.TILL.OKT
DELTA = DELTA
ERF = FELF
ERF.PRECISE = FELF.EXAKT
ERFC = FELFK
ERFC.PRECISE = FELFK.EXAKT
GESTEP = SLSTEG
HEX2BIN = HEX.TILL.BIN
HEX2DEC = HEX.TILL.DEC
HEX2OCT = HEX.TILL.OKT
IMABS = IMABS
IMAGINARY = IMAGINÄR
IMARGUMENT = IMARGUMENT
IMCONJUGATE = IMKONJUGAT
IMCOS = IMCOS
IMCOSH = IMCOSH
IMCOT = IMCOT
IMCSC = IMCSC
IMCSCH = IMCSCH
IMDIV = IMDIV
IMEXP = IMEUPPHÖJT
IMLN = IMLN
IMLOG10 = IMLOG10
IMLOG2 = IMLOG2
IMPOWER = IMUPPHÖJT
IMPRODUCT = IMPRODUKT
IMREAL = IMREAL
IMSEC = IMSEK
IMSECH = IMSEKH
IMSIN = IMSIN
IMSINH = IMSINH
IMSQRT = IMROT
IMSUB = IMDIFF
IMSUM = IMSUM
IMTAN = IMTAN
OCT2BIN = OKT.TILL.BIN
OCT2DEC = OKT.TILL.DEC
OCT2HEX = OKT.TILL.HEX
##
## Finansiella funktioner (Financial Functions)
##
ACCRINT = UPPLRÄNTA
ACCRINTM = UPPLOBLRÄNTA
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = KUPDAGBB
COUPDAYS = KUPDAGB
COUPDAYSNC = KUPDAGNK
COUPNCD = KUPNKD
COUPNUM = KUPANT
COUPPCD = KUPFKD
CUMIPMT = KUMRÄNTA
CUMPRINC = KUMPRIS
DB = DB
DDB = DEGAVSKR
DISC = DISK
DOLLARDE = DECTAL
DOLLARFR = BRÅK
DURATION = LÖPTID
EFFECT = EFFRÄNTA
FV = SLUTVÄRDE
FVSCHEDULE = FÖRRÄNTNING
INTRATE = ÅRSRÄNTA
IPMT = RBETALNING
IRR = IR
ISPMT = RALÅN
MDURATION = MLÖPTID
MIRR = MODIR
NOMINAL = NOMRÄNTA
NPER = PERIODER
NPV = NETNUVÄRDE
ODDFPRICE = UDDAFPRIS
ODDFYIELD = UDDAFAVKASTNING
ODDLPRICE = UDDASPRIS
ODDLYIELD = UDDASAVKASTNING
PDURATION = PLÖPTID
PMT = BETALNING
PPMT = AMORT
PRICE = PRIS
PRICEDISC = PRISDISK
PRICEMAT = PRISFÖRF
PV = NUVÄRDE
RATE = RÄNTA
RECEIVED = BELOPP
RRI = AVKPÅINVEST
SLN = LINAVSKR
SYD = ÅRSAVSKR
TBILLEQ = SSVXEKV
TBILLPRICE = SSVXPRIS
TBILLYIELD = SSVXRÄNTA
VDB = VDEGRAVSKR
XIRR = XIRR
XNPV = XNUVÄRDE
YIELD = NOMAVK
YIELDDISC = NOMAVKDISK
YIELDMAT = NOMAVKFÖRF
##
## Informationsfunktioner (Information Functions)
##
CELL = CELL
ERROR.TYPE = FEL.TYP
INFO = INFO
ISBLANK = ÄRTOM
ISERR = ÄRF
ISERROR = ÄRFEL
ISEVEN = ÄRJÄMN
ISFORMULA = ÄRFORMEL
ISLOGICAL = ÄRLOGISK
ISNA = ÄRSAKNAD
ISNONTEXT = ÄREJTEXT
ISNUMBER = ÄRTAL
ISODD = ÄRUDDA
ISREF = ÄRREF
ISTEXT = ÄRTEXT
N = N
NA = SAKNAS
SHEET = BLAD
SHEETS = ANTALBLAD
TYPE = VÄRDETYP
##
## Logiska funktioner (Logical Functions)
##
AND = OCH
FALSE = FALSKT
IF = OM
IFERROR = OMFEL
IFNA = OMSAKNAS
IFS = IFS
NOT = ICKE
OR = ELLER
SWITCH = VÄXLA
TRUE = SANT
XOR = XELLER
##
## Sök- och referensfunktioner (Lookup & Reference Functions)
##
ADDRESS = ADRESS
AREAS = OMRÅDEN
CHOOSE = VÄLJ
COLUMN = KOLUMN
COLUMNS = KOLUMNER
FORMULATEXT = FORMELTEXT
GETPIVOTDATA = HÄMTA.PIVOTDATA
HLOOKUP = LETAKOLUMN
HYPERLINK = HYPERLÄNK
INDEX = INDEX
INDIRECT = INDIREKT
LOOKUP = LETAUPP
MATCH = PASSA
OFFSET = FÖRSKJUTNING
ROW = RAD
ROWS = RADER
RTD = RTD
TRANSPOSE = TRANSPONERA
VLOOKUP = LETARAD
*RC = RK
##
## Matematiska och trigonometriska funktioner (Math & Trig Functions)
##
ABS = ABS
ACOS = ARCCOS
ACOSH = ARCCOSH
ACOT = ARCCOT
ACOTH = ARCCOTH
AGGREGATE = MÄNGD
ARABIC = ARABISKA
ASIN = ARCSIN
ASINH = ARCSINH
ATAN = ARCTAN
ATAN2 = ARCTAN2
ATANH = ARCTANH
BASE = BAS
CEILING.MATH = RUNDA.UPP.MATEMATISKT
CEILING.PRECISE = RUNDA.UPP.EXAKT
COMBIN = KOMBIN
COMBINA = KOMBINA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DECIMAL
DEGREES = GRADER
ECMA.CEILING = ECMA.RUNDA.UPP
EVEN = JÄMN
EXP = EXP
FACT = FAKULTET
FACTDOUBLE = DUBBELFAKULTET
FLOOR.MATH = RUNDA.NER.MATEMATISKT
FLOOR.PRECISE = RUNDA.NER.EXAKT
GCD = SGD
INT = HELTAL
ISO.CEILING = ISO.RUNDA.UPP
LCM = MGM
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MDETERM
MINVERSE = MINVERT
MMULT = MMULT
MOD = REST
MROUND = MAVRUNDA
MULTINOMIAL = MULTINOMIAL
MUNIT = MENHET
ODD = UDDA
PI = PI
POWER = UPPHÖJT.TILL
PRODUCT = PRODUKT
QUOTIENT = KVOT
RADIANS = RADIANER
RAND = SLUMP
RANDBETWEEN = SLUMP.MELLAN
ROMAN = ROMERSK
ROUND = AVRUNDA
ROUNDBAHTDOWN = AVRUNDABAHTNEDÅT
ROUNDBAHTUP = AVRUNDABAHTUPPÅT
ROUNDDOWN = AVRUNDA.NEDÅT
ROUNDUP = AVRUNDA.UPPÅT
SEC = SEK
SECH = SEKH
SERIESSUM = SERIESUMMA
SIGN = TECKEN
SIN = SIN
SINH = SINH
SQRT = ROT
SQRTPI = ROTPI
SUBTOTAL = DELSUMMA
SUM = SUMMA
SUMIF = SUMMA.OM
SUMIFS = SUMMA.OMF
SUMPRODUCT = PRODUKTSUMMA
SUMSQ = KVADRATSUMMA
SUMX2MY2 = SUMMAX2MY2
SUMX2PY2 = SUMMAX2PY2
SUMXMY2 = SUMMAXMY2
TAN = TAN
TANH = TANH
TRUNC = AVKORTA
##
## Statistiska funktioner (Statistical Functions)
##
AVEDEV = MEDELAVV
AVERAGE = MEDEL
AVERAGEA = AVERAGEA
AVERAGEIF = MEDEL.OM
AVERAGEIFS = MEDEL.OMF
BETA.DIST = BETA.FÖRD
BETA.INV = BETA.INV
BINOM.DIST = BINOM.FÖRD
BINOM.DIST.RANGE = BINOM.FÖRD.INTERVALL
BINOM.INV = BINOM.INV
CHISQ.DIST = CHI2.FÖRD
CHISQ.DIST.RT = CHI2.FÖRD.RT
CHISQ.INV = CHI2.INV
CHISQ.INV.RT = CHI2.INV.RT
CHISQ.TEST = CHI2.TEST
CONFIDENCE.NORM = KONFIDENS.NORM
CONFIDENCE.T = KONFIDENS.T
CORREL = KORREL
COUNT = ANTAL
COUNTA = ANTALV
COUNTBLANK = ANTAL.TOMMA
COUNTIF = ANTAL.OM
COUNTIFS = ANTAL.OMF
COVARIANCE.P = KOVARIANS.P
COVARIANCE.S = KOVARIANS.S
DEVSQ = KVADAVV
EXPON.DIST = EXPON.FÖRD
F.DIST = F.FÖRD
F.DIST.RT = F.FÖRD.RT
F.INV = F.INV
F.INV.RT = F.INV.RT
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = PROGNOS.ETS
FORECAST.ETS.CONFINT = PROGNOS.ETS.KONFINT
FORECAST.ETS.SEASONALITY = PROGNOS.ETS.SÄSONGSBEROENDE
FORECAST.ETS.STAT = PROGNOS.ETS.STAT
FORECAST.LINEAR = PROGNOS.LINJÄR
FREQUENCY = FREKVENS
GAMMA = GAMMA
GAMMA.DIST = GAMMA.FÖRD
GAMMA.INV = GAMMA.INV
GAMMALN = GAMMALN
GAMMALN.PRECISE = GAMMALN.EXAKT
GAUSS = GAUSS
GEOMEAN = GEOMEDEL
GROWTH = EXPTREND
HARMEAN = HARMMEDEL
HYPGEOM.DIST = HYPGEOM.FÖRD
INTERCEPT = SKÄRNINGSPUNKT
KURT = TOPPIGHET
LARGE = STÖRSTA
LINEST = REGR
LOGEST = EXPREGR
LOGNORM.DIST = LOGNORM.FÖRD
LOGNORM.INV = LOGNORM.INV
MAX = MAX
MAXA = MAXA
MAXIFS = MAXIFS
MEDIAN = MEDIAN
MIN = MIN
MINA = MINA
MINIFS = MINIFS
MODE.MULT = TYPVÄRDE.FLERA
MODE.SNGL = TYPVÄRDE.ETT
NEGBINOM.DIST = NEGBINOM.FÖRD
NORM.DIST = NORM.FÖRD
NORM.INV = NORM.INV
NORM.S.DIST = NORM.S.FÖRD
NORM.S.INV = NORM.S.INV
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTIL.EXK
PERCENTILE.INC = PERCENTIL.INK
PERCENTRANK.EXC = PROCENTRANG.EXK
PERCENTRANK.INC = PROCENTRANG.INK
PERMUT = PERMUT
PERMUTATIONA = PERMUTATIONA
PHI = PHI
POISSON.DIST = POISSON.FÖRD
PROB = SANNOLIKHET
QUARTILE.EXC = KVARTIL.EXK
QUARTILE.INC = KVARTIL.INK
RANK.AVG = RANG.MED
RANK.EQ = RANG.EKV
RSQ = RKV
SKEW = SNEDHET
SKEW.P = SNEDHET.P
SLOPE = LUTNING
SMALL = MINSTA
STANDARDIZE = STANDARDISERA
STDEV.P = STDAV.P
STDEV.S = STDAV.S
STDEVA = STDEVA
STDEVPA = STDEVPA
STEYX = STDFELYX
T.DIST = T.FÖRD
T.DIST.2T = T.FÖRD.2T
T.DIST.RT = T.FÖRD.RT
T.INV = T.INV
T.INV.2T = T.INV.2T
T.TEST = T.TEST
TREND = TREND
TRIMMEAN = TRIMMEDEL
VAR.P = VARIANS.P
VAR.S = VARIANS.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = WEIBULL.FÖRD
Z.TEST = Z.TEST
##
## Textfunktioner (Text Functions)
##
BAHTTEXT = BAHTTEXT
CHAR = TECKENKOD
CLEAN = STÄDA
CODE = KOD
CONCAT = SAMMAN
DOLLAR = VALUTA
EXACT = EXAKT
FIND = HITTA
FIXED = FASTTAL
LEFT = VÄNSTER
LEN = LÄNGD
LOWER = GEMENER
MID = EXTEXT
NUMBERVALUE = TALVÄRDE
PROPER = INITIAL
REPLACE = ERSÄTT
REPT = REP
RIGHT = HÖGER
SEARCH = SÖK
SUBSTITUTE = BYT.UT
T = T
TEXT = TEXT
TEXTJOIN = TEXTJOIN
THAIDIGIT = THAISIFFRA
THAINUMSOUND = THAITALLJUD
THAINUMSTRING = THAITALSTRÄNG
THAISTRINGLENGTH = THAISTRÄNGLÄNGD
TRIM = RENSA
UNICHAR = UNITECKENKOD
UNICODE = UNICODE
UPPER = VERSALER
VALUE = TEXTNUM
##
## Webbfunktioner (Web Functions)
##
ENCODEURL = KODAWEBBADRESS
FILTERXML = FILTRERAXML
WEBSERVICE = WEBBTJÄNST
##
## Kompatibilitetsfunktioner (Compatibility Functions)
##
BETADIST = BETAFÖRD
BETAINV = BETAINV
BINOMDIST = BINOMFÖRD
CEILING = RUNDA.UPP
CHIDIST = CHI2FÖRD
CHIINV = CHI2INV
CHITEST = CHI2TEST
CONCATENATE = SAMMANFOGA
CONFIDENCE = KONFIDENS
COVAR = KOVAR
CRITBINOM = KRITBINOM
EXPONDIST = EXPONFÖRD
FDIST = FFÖRD
FINV = FINV
FLOOR = RUNDA.NER
FORECAST = PREDIKTION
FTEST = FTEST
GAMMADIST = GAMMAFÖRD
GAMMAINV = GAMMAINV
HYPGEOMDIST = HYPGEOMFÖRD
LOGINV = LOGINV
LOGNORMDIST = LOGNORMFÖRD
MODE = TYPVÄRDE
NEGBINOMDIST = NEGBINOMFÖRD
NORMDIST = NORMFÖRD
NORMINV = NORMINV
NORMSDIST = NORMSFÖRD
NORMSINV = NORMSINV
PERCENTILE = PERCENTIL
PERCENTRANK = PROCENTRANG
POISSON = POISSON
QUARTILE = KVARTIL
RANK = RANG
STDEV = STDAV
STDEVP = STDAVP
TDIST = TFÖRD
TINV = TINV
TTEST = TTEST
VAR = VARIANS
VARP = VARIANSP
WEIBULL = WEIBULL
ZTEST = ZTEST
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config 0000644 00000000542 15167673465 0020633 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Svenska (Swedish)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #SKÄRNING!
DIV0 = #DIVISION/0!
VALUE = #VÄRDEFEL!
REF = #REFERENS!
NAME = #NAMN?
NUM = #OGILTIGT!
NA = #SAKNAS!
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions 0000644 00000025073 15167673465 0021370 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Magyar (Hungarian)
##
############################################################
##
## Kockafüggvények (Cube Functions)
##
CUBEKPIMEMBER = KOCKA.FŐTELJMUT
CUBEMEMBER = KOCKA.TAG
CUBEMEMBERPROPERTY = KOCKA.TAG.TUL
CUBERANKEDMEMBER = KOCKA.HALM.ELEM
CUBESET = KOCKA.HALM
CUBESETCOUNT = KOCKA.HALM.DB
CUBEVALUE = KOCKA.ÉRTÉK
##
## Adatbázis-kezelő függvények (Database Functions)
##
DAVERAGE = AB.ÁTLAG
DCOUNT = AB.DARAB
DCOUNTA = AB.DARAB2
DGET = AB.MEZŐ
DMAX = AB.MAX
DMIN = AB.MIN
DPRODUCT = AB.SZORZAT
DSTDEV = AB.SZÓRÁS
DSTDEVP = AB.SZÓRÁS2
DSUM = AB.SZUM
DVAR = AB.VAR
DVARP = AB.VAR2
##
## Dátumfüggvények (Date & Time Functions)
##
DATE = DÁTUM
DATEDIF = DÁTUMTÓLIG
DATESTRING = DÁTUMSZÖVEG
DATEVALUE = DÁTUMÉRTÉK
DAY = NAP
DAYS = NAPOK
DAYS360 = NAP360
EDATE = KALK.DÁTUM
EOMONTH = HÓNAP.UTOLSÓ.NAP
HOUR = ÓRA
ISOWEEKNUM = ISO.HÉT.SZÁMA
MINUTE = PERCEK
MONTH = HÓNAP
NETWORKDAYS = ÖSSZ.MUNKANAP
NETWORKDAYS.INTL = ÖSSZ.MUNKANAP.INTL
NOW = MOST
SECOND = MPERC
THAIDAYOFWEEK = THAIHÉTNAPJA
THAIMONTHOFYEAR = THAIHÓNAP
THAIYEAR = THAIÉV
TIME = IDŐ
TIMEVALUE = IDŐÉRTÉK
TODAY = MA
WEEKDAY = HÉT.NAPJA
WEEKNUM = HÉT.SZÁMA
WORKDAY = KALK.MUNKANAP
WORKDAY.INTL = KALK.MUNKANAP.INTL
YEAR = ÉV
YEARFRAC = TÖRTÉV
##
## Mérnöki függvények (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BIN.DEC
BIN2HEX = BIN.HEX
BIN2OCT = BIN.OKT
BITAND = BIT.ÉS
BITLSHIFT = BIT.BAL.ELTOL
BITOR = BIT.VAGY
BITRSHIFT = BIT.JOBB.ELTOL
BITXOR = BIT.XVAGY
COMPLEX = KOMPLEX
CONVERT = KONVERTÁLÁS
DEC2BIN = DEC.BIN
DEC2HEX = DEC.HEX
DEC2OCT = DEC.OKT
DELTA = DELTA
ERF = HIBAF
ERF.PRECISE = HIBAF.PONTOS
ERFC = HIBAF.KOMPLEMENTER
ERFC.PRECISE = HIBAFKOMPLEMENTER.PONTOS
GESTEP = KÜSZÖBNÉL.NAGYOBB
HEX2BIN = HEX.BIN
HEX2DEC = HEX.DEC
HEX2OCT = HEX.OKT
IMABS = KÉPZ.ABSZ
IMAGINARY = KÉPZETES
IMARGUMENT = KÉPZ.ARGUMENT
IMCONJUGATE = KÉPZ.KONJUGÁLT
IMCOS = KÉPZ.COS
IMCOSH = KÉPZ.COSH
IMCOT = KÉPZ.COT
IMCSC = KÉPZ.CSC
IMCSCH = KÉPZ.CSCH
IMDIV = KÉPZ.HÁNYAD
IMEXP = KÉPZ.EXP
IMLN = KÉPZ.LN
IMLOG10 = KÉPZ.LOG10
IMLOG2 = KÉPZ.LOG2
IMPOWER = KÉPZ.HATV
IMPRODUCT = KÉPZ.SZORZAT
IMREAL = KÉPZ.VALÓS
IMSEC = KÉPZ.SEC
IMSECH = KÉPZ.SECH
IMSIN = KÉPZ.SIN
IMSINH = KÉPZ.SINH
IMSQRT = KÉPZ.GYÖK
IMSUB = KÉPZ.KÜL
IMSUM = KÉPZ.ÖSSZEG
IMTAN = KÉPZ.TAN
OCT2BIN = OKT.BIN
OCT2DEC = OKT.DEC
OCT2HEX = OKT.HEX
##
## Pénzügyi függvények (Financial Functions)
##
ACCRINT = IDŐSZAKI.KAMAT
ACCRINTM = LEJÁRATI.KAMAT
AMORDEGRC = ÉRTÉKCSÖKK.TÉNYEZŐVEL
AMORLINC = ÉRTÉKCSÖKK
COUPDAYBS = SZELVÉNYIDŐ.KEZDETTŐL
COUPDAYS = SZELVÉNYIDŐ
COUPDAYSNC = SZELVÉNYIDŐ.KIFIZETÉSTŐL
COUPNCD = ELSŐ.SZELVÉNYDÁTUM
COUPNUM = SZELVÉNYSZÁM
COUPPCD = UTOLSÓ.SZELVÉNYDÁTUM
CUMIPMT = ÖSSZES.KAMAT
CUMPRINC = ÖSSZES.TŐKERÉSZ
DB = KCS2
DDB = KCSA
DISC = LESZÁM
DOLLARDE = FORINT.DEC
DOLLARFR = FORINT.TÖRT
DURATION = KAMATÉRZ
EFFECT = TÉNYLEGES
FV = JBÉ
FVSCHEDULE = KJÉ
INTRATE = KAMATRÁTA
IPMT = RRÉSZLET
IRR = BMR
ISPMT = LRÉSZLETKAMAT
MDURATION = MKAMATÉRZ
MIRR = MEGTÉRÜLÉS
NOMINAL = NÉVLEGES
NPER = PER.SZÁM
NPV = NMÉ
ODDFPRICE = ELTÉRŐ.EÁR
ODDFYIELD = ELTÉRŐ.EHOZAM
ODDLPRICE = ELTÉRŐ.UÁR
ODDLYIELD = ELTÉRŐ.UHOZAM
PDURATION = KAMATÉRZ.PER
PMT = RÉSZLET
PPMT = PRÉSZLET
PRICE = ÁR
PRICEDISC = ÁR.LESZÁM
PRICEMAT = ÁR.LEJÁRAT
PV = MÉ
RATE = RÁTA
RECEIVED = KAPOTT
RRI = MR
SLN = LCSA
SYD = ÉSZÖ
TBILLEQ = KJEGY.EGYENÉRT
TBILLPRICE = KJEGY.ÁR
TBILLYIELD = KJEGY.HOZAM
VDB = ÉCSRI
XIRR = XBMR
XNPV = XNJÉ
YIELD = HOZAM
YIELDDISC = HOZAM.LESZÁM
YIELDMAT = HOZAM.LEJÁRAT
##
## Információs függvények (Information Functions)
##
CELL = CELLA
ERROR.TYPE = HIBA.TÍPUS
INFO = INFÓ
ISBLANK = ÜRES
ISERR = HIBA.E
ISERROR = HIBÁS
ISEVEN = PÁROSE
ISFORMULA = KÉPLET
ISLOGICAL = LOGIKAI
ISNA = NINCS
ISNONTEXT = NEM.SZÖVEG
ISNUMBER = SZÁM
ISODD = PÁRATLANE
ISREF = HIVATKOZÁS
ISTEXT = SZÖVEG.E
N = S
NA = HIÁNYZIK
SHEET = LAP
SHEETS = LAPOK
TYPE = TÍPUS
##
## Logikai függvények (Logical Functions)
##
AND = ÉS
FALSE = HAMIS
IF = HA
IFERROR = HAHIBA
IFNA = HAHIÁNYZIK
IFS = HAELSŐIGAZ
NOT = NEM
OR = VAGY
SWITCH = ÁTVÁLT
TRUE = IGAZ
XOR = XVAGY
##
## Keresési és hivatkozási függvények (Lookup & Reference Functions)
##
ADDRESS = CÍM
AREAS = TERÜLET
CHOOSE = VÁLASZT
COLUMN = OSZLOP
COLUMNS = OSZLOPOK
FORMULATEXT = KÉPLETSZÖVEG
GETPIVOTDATA = KIMUTATÁSADATOT.VESZ
HLOOKUP = VKERES
HYPERLINK = HIPERHIVATKOZÁS
INDEX = INDEX
INDIRECT = INDIREKT
LOOKUP = KERES
MATCH = HOL.VAN
OFFSET = ELTOLÁS
ROW = SOR
ROWS = SOROK
RTD = VIA
TRANSPOSE = TRANSZPONÁLÁS
VLOOKUP = FKERES
*RC = SO
##
## Matematikai és trigonometrikus függvények (Math & Trig Functions)
##
ABS = ABS
ACOS = ARCCOS
ACOSH = ACOSH
ACOT = ARCCOT
ACOTH = ARCCOTH
AGGREGATE = ÖSSZESÍT
ARABIC = ARAB
ASIN = ARCSIN
ASINH = ASINH
ATAN = ARCTAN
ATAN2 = ARCTAN2
ATANH = ATANH
BASE = ALAP
CEILING.MATH = PLAFON.MAT
CEILING.PRECISE = PLAFON.PONTOS
COMBIN = KOMBINÁCIÓK
COMBINA = KOMBINÁCIÓK.ISM
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = TIZEDES
DEGREES = FOK
ECMA.CEILING = ECMA.PLAFON
EVEN = PÁROS
EXP = KITEVŐ
FACT = FAKT
FACTDOUBLE = FAKTDUPLA
FLOOR.MATH = PADLÓ.MAT
FLOOR.PRECISE = PADLÓ.PONTOS
GCD = LKO
INT = INT
ISO.CEILING = ISO.PLAFON
LCM = LKT
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MDETERM
MINVERSE = INVERZ.MÁTRIX
MMULT = MSZORZAT
MOD = MARADÉK
MROUND = TÖBBSZ.KEREKÍT
MULTINOMIAL = SZORHÁNYFAKT
MUNIT = MMÁTRIX
ODD = PÁRATLAN
PI = PI
POWER = HATVÁNY
PRODUCT = SZORZAT
QUOTIENT = KVÓCIENS
RADIANS = RADIÁN
RAND = VÉL
RANDBETWEEN = VÉLETLEN.KÖZÖTT
ROMAN = RÓMAI
ROUND = KEREKÍTÉS
ROUNDBAHTDOWN = BAHTKEREK.LE
ROUNDBAHTUP = BAHTKEREK.FEL
ROUNDDOWN = KEREK.LE
ROUNDUP = KEREK.FEL
SEC = SEC
SECH = SECH
SERIESSUM = SORÖSSZEG
SIGN = ELŐJEL
SIN = SIN
SINH = SINH
SQRT = GYÖK
SQRTPI = GYÖKPI
SUBTOTAL = RÉSZÖSSZEG
SUM = SZUM
SUMIF = SZUMHA
SUMIFS = SZUMHATÖBB
SUMPRODUCT = SZORZATÖSSZEG
SUMSQ = NÉGYZETÖSSZEG
SUMX2MY2 = SZUMX2BŐLY2
SUMX2PY2 = SZUMX2MEGY2
SUMXMY2 = SZUMXBŐLY2
TAN = TAN
TANH = TANH
TRUNC = CSONK
##
## Statisztikai függvények (Statistical Functions)
##
AVEDEV = ÁTL.ELTÉRÉS
AVERAGE = ÁTLAG
AVERAGEA = ÁTLAGA
AVERAGEIF = ÁTLAGHA
AVERAGEIFS = ÁTLAGHATÖBB
BETA.DIST = BÉTA.ELOSZL
BETA.INV = BÉTA.INVERZ
BINOM.DIST = BINOM.ELOSZL
BINOM.DIST.RANGE = BINOM.ELOSZL.TART
BINOM.INV = BINOM.INVERZ
CHISQ.DIST = KHINÉGYZET.ELOSZLÁS
CHISQ.DIST.RT = KHINÉGYZET.ELOSZLÁS.JOBB
CHISQ.INV = KHINÉGYZET.INVERZ
CHISQ.INV.RT = KHINÉGYZET.INVERZ.JOBB
CHISQ.TEST = KHINÉGYZET.PRÓBA
CONFIDENCE.NORM = MEGBÍZHATÓSÁG.NORM
CONFIDENCE.T = MEGBÍZHATÓSÁG.T
CORREL = KORREL
COUNT = DARAB
COUNTA = DARAB2
COUNTBLANK = DARABÜRES
COUNTIF = DARABTELI
COUNTIFS = DARABHATÖBB
COVARIANCE.P = KOVARIANCIA.S
COVARIANCE.S = KOVARIANCIA.M
DEVSQ = SQ
EXPON.DIST = EXP.ELOSZL
F.DIST = F.ELOSZL
F.DIST.RT = F.ELOSZLÁS.JOBB
F.INV = F.INVERZ
F.INV.RT = F.INVERZ.JOBB
F.TEST = F.PRÓB
FISHER = FISHER
FISHERINV = INVERZ.FISHER
FORECAST.ETS = ELŐREJELZÉS.ESIM
FORECAST.ETS.CONFINT = ELŐREJELZÉS.ESIM.KONFINT
FORECAST.ETS.SEASONALITY = ELŐREJELZÉS.ESIM.SZEZONALITÁS
FORECAST.ETS.STAT = ELŐREJELZÉS.ESIM.STAT
FORECAST.LINEAR = ELŐREJELZÉS.LINEÁRIS
FREQUENCY = GYAKORISÁG
GAMMA = GAMMA
GAMMA.DIST = GAMMA.ELOSZL
GAMMA.INV = GAMMA.INVERZ
GAMMALN = GAMMALN
GAMMALN.PRECISE = GAMMALN.PONTOS
GAUSS = GAUSS
GEOMEAN = MÉRTANI.KÖZÉP
GROWTH = NÖV
HARMEAN = HARM.KÖZÉP
HYPGEOM.DIST = HIPGEOM.ELOSZLÁS
INTERCEPT = METSZ
KURT = CSÚCSOSSÁG
LARGE = NAGY
LINEST = LIN.ILL
LOGEST = LOG.ILL
LOGNORM.DIST = LOGNORM.ELOSZLÁS
LOGNORM.INV = LOGNORM.INVERZ
MAX = MAX
MAXA = MAXA
MAXIFS = MAXHA
MEDIAN = MEDIÁN
MIN = MIN
MINA = MIN2
MINIFS = MINHA
MODE.MULT = MÓDUSZ.TÖBB
MODE.SNGL = MÓDUSZ.EGY
NEGBINOM.DIST = NEGBINOM.ELOSZLÁS
NORM.DIST = NORM.ELOSZLÁS
NORM.INV = NORM.INVERZ
NORM.S.DIST = NORM.S.ELOSZLÁS
NORM.S.INV = NORM.S.INVERZ
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTILIS.KIZÁR
PERCENTILE.INC = PERCENTILIS.TARTALMAZ
PERCENTRANK.EXC = SZÁZALÉKRANG.KIZÁR
PERCENTRANK.INC = SZÁZALÉKRANG.TARTALMAZ
PERMUT = VARIÁCIÓK
PERMUTATIONA = VARIÁCIÓK.ISM
PHI = FI
POISSON.DIST = POISSON.ELOSZLÁS
PROB = VALÓSZÍNŰSÉG
QUARTILE.EXC = KVARTILIS.KIZÁR
QUARTILE.INC = KVARTILIS.TARTALMAZ
RANK.AVG = RANG.ÁTL
RANK.EQ = RANG.EGY
RSQ = RNÉGYZET
SKEW = FERDESÉG
SKEW.P = FERDESÉG.P
SLOPE = MEREDEKSÉG
SMALL = KICSI
STANDARDIZE = NORMALIZÁLÁS
STDEV.P = SZÓR.S
STDEV.S = SZÓR.M
STDEVA = SZÓRÁSA
STDEVPA = SZÓRÁSPA
STEYX = STHIBAYX
T.DIST = T.ELOSZL
T.DIST.2T = T.ELOSZLÁS.2SZ
T.DIST.RT = T.ELOSZLÁS.JOBB
T.INV = T.INVERZ
T.INV.2T = T.INVERZ.2SZ
T.TEST = T.PRÓB
TREND = TREND
TRIMMEAN = RÉSZÁTLAG
VAR.P = VAR.S
VAR.S = VAR.M
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = WEIBULL.ELOSZLÁS
Z.TEST = Z.PRÓB
##
## Szövegműveletekhez használható függvények (Text Functions)
##
BAHTTEXT = BAHTSZÖVEG
CHAR = KARAKTER
CLEAN = TISZTÍT
CODE = KÓD
CONCAT = FŰZ
DOLLAR = FORINT
EXACT = AZONOS
FIND = SZÖVEG.TALÁL
FIXED = FIX
ISTHAIDIGIT = ON.THAI.NUMERO
LEFT = BAL
LEN = HOSSZ
LOWER = KISBETŰ
MID = KÖZÉP
NUMBERSTRING = SZÁM.BETŰVEL
NUMBERVALUE = SZÁMÉRTÉK
PHONETIC = FONETIKUS
PROPER = TNÉV
REPLACE = CSERE
REPT = SOKSZOR
RIGHT = JOBB
SEARCH = SZÖVEG.KERES
SUBSTITUTE = HELYETTE
T = T
TEXT = SZÖVEG
TEXTJOIN = SZÖVEGÖSSZEFŰZÉS
THAIDIGIT = THAISZÁM
THAINUMSOUND = THAISZÁMHANG
THAINUMSTRING = THAISZÁMKAR
THAISTRINGLENGTH = THAIKARHOSSZ
TRIM = KIMETSZ
UNICHAR = UNIKARAKTER
UNICODE = UNICODE
UPPER = NAGYBETŰS
VALUE = ÉRTÉK
##
## Webes függvények (Web Functions)
##
ENCODEURL = URL.KÓDOL
FILTERXML = XMLSZŰRÉS
WEBSERVICE = WEBSZOLGÁLTATÁS
##
## Kompatibilitási függvények (Compatibility Functions)
##
BETADIST = BÉTA.ELOSZLÁS
BETAINV = INVERZ.BÉTA
BINOMDIST = BINOM.ELOSZLÁS
CEILING = PLAFON
CHIDIST = KHI.ELOSZLÁS
CHIINV = INVERZ.KHI
CHITEST = KHI.PRÓBA
CONCATENATE = ÖSSZEFŰZ
CONFIDENCE = MEGBÍZHATÓSÁG
COVAR = KOVAR
CRITBINOM = KRITBINOM
EXPONDIST = EXP.ELOSZLÁS
FDIST = F.ELOSZLÁS
FINV = INVERZ.F
FLOOR = PADLÓ
FORECAST = ELŐREJELZÉS
FTEST = F.PRÓBA
GAMMADIST = GAMMA.ELOSZLÁS
GAMMAINV = INVERZ.GAMMA
HYPGEOMDIST = HIPERGEOM.ELOSZLÁS
LOGINV = INVERZ.LOG.ELOSZLÁS
LOGNORMDIST = LOG.ELOSZLÁS
MODE = MÓDUSZ
NEGBINOMDIST = NEGBINOM.ELOSZL
NORMDIST = NORM.ELOSZL
NORMINV = INVERZ.NORM
NORMSDIST = STNORMELOSZL
NORMSINV = INVERZ.STNORM
PERCENTILE = PERCENTILIS
PERCENTRANK = SZÁZALÉKRANG
POISSON = POISSON
QUARTILE = KVARTILIS
RANK = SORSZÁM
STDEV = SZÓRÁS
STDEVP = SZÓRÁSP
TDIST = T.ELOSZLÁS
TINV = INVERZ.T
TTEST = T.PRÓBA
VAR = VAR
VARP = VARP
WEIBULL = WEIBULL
ZTEST = Z.PRÓBA
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config 0000644 00000000531 15167673465 0020615 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Magyar (Hungarian)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #NULLA!
DIV0 = #ZÉRÓOSZTÓ!
VALUE = #ÉRTÉK!
REF = #HIV!
NAME = #NÉV?
NUM = #SZÁM!
NA = #HIÁNYZIK
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config 0000644 00000000476 15167673465 0021232 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## English-UK (English-UK)
##
############################################################
ArgumentSeparator = ,
##
## (For future use)
##
currencySymbol = £
##
## Error Codes
##
NULL
DIV0
VALUE
REF
NAME
NUM
NA
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions 0000644 00000026262 15167673465 0021353 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Suomi (Finnish)
##
############################################################
##
## Kuutiofunktiot (Cube Functions)
##
CUBEKPIMEMBER = KUUTIOKPIJÄSEN
CUBEMEMBER = KUUTIONJÄSEN
CUBEMEMBERPROPERTY = KUUTIONJÄSENENOMINAISUUS
CUBERANKEDMEMBER = KUUTIONLUOKITELTUJÄSEN
CUBESET = KUUTIOJOUKKO
CUBESETCOUNT = KUUTIOJOUKKOJENMÄÄRÄ
CUBEVALUE = KUUTIONARVO
##
## Tietokantafunktiot (Database Functions)
##
DAVERAGE = TKESKIARVO
DCOUNT = TLASKE
DCOUNTA = TLASKEA
DGET = TNOUDA
DMAX = TMAKS
DMIN = TMIN
DPRODUCT = TTULO
DSTDEV = TKESKIHAJONTA
DSTDEVP = TKESKIHAJONTAP
DSUM = TSUMMA
DVAR = TVARIANSSI
DVARP = TVARIANSSIP
##
## Päivämäärä- ja aikafunktiot (Date & Time Functions)
##
DATE = PÄIVÄYS
DATEDIF = PVMERO
DATESTRING = PVMMERKKIJONO
DATEVALUE = PÄIVÄYSARVO
DAY = PÄIVÄ
DAYS = PÄIVÄT
DAYS360 = PÄIVÄT360
EDATE = PÄIVÄ.KUUKAUSI
EOMONTH = KUUKAUSI.LOPPU
HOUR = TUNNIT
ISOWEEKNUM = VIIKKO.ISO.NRO
MINUTE = MINUUTIT
MONTH = KUUKAUSI
NETWORKDAYS = TYÖPÄIVÄT
NETWORKDAYS.INTL = TYÖPÄIVÄT.KANSVÄL
NOW = NYT
SECOND = SEKUNNIT
THAIDAYOFWEEK = THAI.VIIKONPÄIVÄ
THAIMONTHOFYEAR = THAI.KUUKAUSI
THAIYEAR = THAI.VUOSI
TIME = AIKA
TIMEVALUE = AIKA_ARVO
TODAY = TÄMÄ.PÄIVÄ
WEEKDAY = VIIKONPÄIVÄ
WEEKNUM = VIIKKO.NRO
WORKDAY = TYÖPÄIVÄ
WORKDAY.INTL = TYÖPÄIVÄ.KANSVÄL
YEAR = VUOSI
YEARFRAC = VUOSI.OSA
##
## Tekniset funktiot (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BINDES
BIN2HEX = BINHEKSA
BIN2OCT = BINOKT
BITAND = BITTI.JA
BITLSHIFT = BITTI.SIIRTO.V
BITOR = BITTI.TAI
BITRSHIFT = BITTI.SIIRTO.O
BITXOR = BITTI.EHDOTON.TAI
COMPLEX = KOMPLEKSI
CONVERT = MUUNNA
DEC2BIN = DESBIN
DEC2HEX = DESHEKSA
DEC2OCT = DESOKT
DELTA = SAMA.ARVO
ERF = VIRHEFUNKTIO
ERF.PRECISE = VIRHEFUNKTIO.TARKKA
ERFC = VIRHEFUNKTIO.KOMPLEMENTTI
ERFC.PRECISE = VIRHEFUNKTIO.KOMPLEMENTTI.TARKKA
GESTEP = RAJA
HEX2BIN = HEKSABIN
HEX2DEC = HEKSADES
HEX2OCT = HEKSAOKT
IMABS = KOMPLEKSI.ABS
IMAGINARY = KOMPLEKSI.IMAG
IMARGUMENT = KOMPLEKSI.ARG
IMCONJUGATE = KOMPLEKSI.KONJ
IMCOS = KOMPLEKSI.COS
IMCOSH = IMCOSH
IMCOT = KOMPLEKSI.COT
IMCSC = KOMPLEKSI.KOSEK
IMCSCH = KOMPLEKSI.KOSEKH
IMDIV = KOMPLEKSI.OSAM
IMEXP = KOMPLEKSI.EKSP
IMLN = KOMPLEKSI.LN
IMLOG10 = KOMPLEKSI.LOG10
IMLOG2 = KOMPLEKSI.LOG2
IMPOWER = KOMPLEKSI.POT
IMPRODUCT = KOMPLEKSI.TULO
IMREAL = KOMPLEKSI.REAALI
IMSEC = KOMPLEKSI.SEK
IMSECH = KOMPLEKSI.SEKH
IMSIN = KOMPLEKSI.SIN
IMSINH = KOMPLEKSI.SINH
IMSQRT = KOMPLEKSI.NELIÖJ
IMSUB = KOMPLEKSI.EROTUS
IMSUM = KOMPLEKSI.SUM
IMTAN = KOMPLEKSI.TAN
OCT2BIN = OKTBIN
OCT2DEC = OKTDES
OCT2HEX = OKTHEKSA
##
## Rahoitusfunktiot (Financial Functions)
##
ACCRINT = KERTYNYT.KORKO
ACCRINTM = KERTYNYT.KORKO.LOPUSSA
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = KORKOPÄIVÄT.ALUSTA
COUPDAYS = KORKOPÄIVÄT
COUPDAYSNC = KORKOPÄIVÄT.SEURAAVA
COUPNCD = KORKOPÄIVÄ.SEURAAVA
COUPNUM = KORKOPÄIVÄ.JAKSOT
COUPPCD = KORKOPÄIVÄ.EDELLINEN
CUMIPMT = MAKSETTU.KORKO
CUMPRINC = MAKSETTU.LYHENNYS
DB = DB
DDB = DDB
DISC = DISKONTTOKORKO
DOLLARDE = VALUUTTA.DES
DOLLARFR = VALUUTTA.MURTO
DURATION = KESTO
EFFECT = KORKO.EFEKT
FV = TULEVA.ARVO
FVSCHEDULE = TULEVA.ARVO.ERIKORKO
INTRATE = KORKO.ARVOPAPERI
IPMT = IPMT
IRR = SISÄINEN.KORKO
ISPMT = ISPMT
MDURATION = KESTO.MUUNN
MIRR = MSISÄINEN
NOMINAL = KORKO.VUOSI
NPER = NJAKSO
NPV = NNA
ODDFPRICE = PARITON.ENS.NIMELLISARVO
ODDFYIELD = PARITON.ENS.TUOTTO
ODDLPRICE = PARITON.VIIM.NIMELLISARVO
ODDLYIELD = PARITON.VIIM.TUOTTO
PDURATION = KESTO.JAKSO
PMT = MAKSU
PPMT = PPMT
PRICE = HINTA
PRICEDISC = HINTA.DISK
PRICEMAT = HINTA.LUNASTUS
PV = NA
RATE = KORKO
RECEIVED = SAATU.HINTA
RRI = TOT.ROI
SLN = STP
SYD = VUOSIPOISTO
TBILLEQ = OBLIG.TUOTTOPROS
TBILLPRICE = OBLIG.HINTA
TBILLYIELD = OBLIG.TUOTTO
VDB = VDB
XIRR = SISÄINEN.KORKO.JAKSOTON
XNPV = NNA.JAKSOTON
YIELD = TUOTTO
YIELDDISC = TUOTTO.DISK
YIELDMAT = TUOTTO.ERÄP
##
## Tietofunktiot (Information Functions)
##
CELL = SOLU
ERROR.TYPE = VIRHEEN.LAJI
INFO = KUVAUS
ISBLANK = ONTYHJÄ
ISERR = ONVIRH
ISERROR = ONVIRHE
ISEVEN = ONPARILLINEN
ISFORMULA = ONKAAVA
ISLOGICAL = ONTOTUUS
ISNA = ONPUUTTUU
ISNONTEXT = ONEI_TEKSTI
ISNUMBER = ONLUKU
ISODD = ONPARITON
ISREF = ONVIITT
ISTEXT = ONTEKSTI
N = N
NA = PUUTTUU
SHEET = TAULUKKO
SHEETS = TAULUKOT
TYPE = TYYPPI
##
## Loogiset funktiot (Logical Functions)
##
AND = JA
FALSE = EPÄTOSI
IF = JOS
IFERROR = JOSVIRHE
IFNA = JOSPUUTTUU
IFS = JOSS
NOT = EI
OR = TAI
SWITCH = MUUTA
TRUE = TOSI
XOR = EHDOTON.TAI
##
## Haku- ja viitefunktiot (Lookup & Reference Functions)
##
ADDRESS = OSOITE
AREAS = ALUEET
CHOOSE = VALITSE.INDEKSI
COLUMN = SARAKE
COLUMNS = SARAKKEET
FORMULATEXT = KAAVA.TEKSTI
GETPIVOTDATA = NOUDA.PIVOT.TIEDOT
HLOOKUP = VHAKU
HYPERLINK = HYPERLINKKI
INDEX = INDEKSI
INDIRECT = EPÄSUORA
LOOKUP = HAKU
MATCH = VASTINE
OFFSET = SIIRTYMÄ
ROW = RIVI
ROWS = RIVIT
RTD = RTD
TRANSPOSE = TRANSPONOI
VLOOKUP = PHAKU
*RC = RS
##
## Matemaattiset ja trigonometriset funktiot (Math & Trig Functions)
##
ABS = ITSEISARVO
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = KOOSTE
ARABIC = ARABIA
ASIN = ASIN
ASINH = ASINH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = PERUS
CEILING.MATH = PYÖRISTÄ.KERR.YLÖS.MATEMAATTINEN
CEILING.PRECISE = PYÖRISTÄ.KERR.YLÖS.TARKKA
COMBIN = KOMBINAATIO
COMBINA = KOMBINAATIOA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = KOSEK
CSCH = KOSEKH
DECIMAL = DESIMAALI
DEGREES = ASTEET
ECMA.CEILING = ECMA.PYÖRISTÄ.KERR.YLÖS
EVEN = PARILLINEN
EXP = EKSPONENTTI
FACT = KERTOMA
FACTDOUBLE = KERTOMA.OSA
FLOOR.MATH = PYÖRISTÄ.KERR.ALAS.MATEMAATTINEN
FLOOR.PRECISE = PYÖRISTÄ.KERR.ALAS.TARKKA
GCD = SUURIN.YHT.TEKIJÄ
INT = KOKONAISLUKU
ISO.CEILING = ISO.PYÖRISTÄ.KERR.YLÖS
LCM = PIENIN.YHT.JAETTAVA
LN = LUONNLOG
LOG = LOG
LOG10 = LOG10
MDETERM = MDETERM
MINVERSE = MKÄÄNTEINEN
MMULT = MKERRO
MOD = JAKOJ
MROUND = PYÖRISTÄ.KERR
MULTINOMIAL = MULTINOMI
MUNIT = YKSIKKÖM
ODD = PARITON
PI = PII
POWER = POTENSSI
PRODUCT = TULO
QUOTIENT = OSAMÄÄRÄ
RADIANS = RADIAANIT
RAND = SATUNNAISLUKU
RANDBETWEEN = SATUNNAISLUKU.VÄLILTÄ
ROMAN = ROMAN
ROUND = PYÖRISTÄ
ROUNDBAHTDOWN = PYÖRISTÄ.BAHT.ALAS
ROUNDBAHTUP = PYÖRISTÄ.BAHT.YLÖS
ROUNDDOWN = PYÖRISTÄ.DES.ALAS
ROUNDUP = PYÖRISTÄ.DES.YLÖS
SEC = SEK
SECH = SEKH
SERIESSUM = SARJA.SUMMA
SIGN = ETUMERKKI
SIN = SIN
SINH = SINH
SQRT = NELIÖJUURI
SQRTPI = NELIÖJUURI.PII
SUBTOTAL = VÄLISUMMA
SUM = SUMMA
SUMIF = SUMMA.JOS
SUMIFS = SUMMA.JOS.JOUKKO
SUMPRODUCT = TULOJEN.SUMMA
SUMSQ = NELIÖSUMMA
SUMX2MY2 = NELIÖSUMMIEN.EROTUS
SUMX2PY2 = NELIÖSUMMIEN.SUMMA
SUMXMY2 = EROTUSTEN.NELIÖSUMMA
TAN = TAN
TANH = TANH
TRUNC = KATKAISE
##
## Tilastolliset funktiot (Statistical Functions)
##
AVEDEV = KESKIPOIKKEAMA
AVERAGE = KESKIARVO
AVERAGEA = KESKIARVOA
AVERAGEIF = KESKIARVO.JOS
AVERAGEIFS = KESKIARVO.JOS.JOUKKO
BETA.DIST = BEETA.JAKAUMA
BETA.INV = BEETA.KÄÄNT
BINOM.DIST = BINOMI.JAKAUMA
BINOM.DIST.RANGE = BINOMI.JAKAUMA.ALUE
BINOM.INV = BINOMIJAKAUMA.KÄÄNT
CHISQ.DIST = CHINELIÖ.JAKAUMA
CHISQ.DIST.RT = CHINELIÖ.JAKAUMA.OH
CHISQ.INV = CHINELIÖ.KÄÄNT
CHISQ.INV.RT = CHINELIÖ.KÄÄNT.OH
CHISQ.TEST = CHINELIÖ.TESTI
CONFIDENCE.NORM = LUOTTAMUSVÄLI.NORM
CONFIDENCE.T = LUOTTAMUSVÄLI.T
CORREL = KORRELAATIO
COUNT = LASKE
COUNTA = LASKE.A
COUNTBLANK = LASKE.TYHJÄT
COUNTIF = LASKE.JOS
COUNTIFS = LASKE.JOS.JOUKKO
COVARIANCE.P = KOVARIANSSI.P
COVARIANCE.S = KOVARIANSSI.S
DEVSQ = OIKAISTU.NELIÖSUMMA
EXPON.DIST = EKSPONENTIAALI.JAKAUMA
F.DIST = F.JAKAUMA
F.DIST.RT = F.JAKAUMA.OH
F.INV = F.KÄÄNT
F.INV.RT = F.KÄÄNT.OH
F.TEST = F.TESTI
FISHER = FISHER
FISHERINV = FISHER.KÄÄNT
FORECAST.ETS = ENNUSTE.ETS
FORECAST.ETS.CONFINT = ENNUSTE.ETS.CONFINT
FORECAST.ETS.SEASONALITY = ENNUSTE.ETS.KAUSIVAIHTELU
FORECAST.ETS.STAT = ENNUSTE.ETS.STAT
FORECAST.LINEAR = ENNUSTE.LINEAARINEN
FREQUENCY = TAAJUUS
GAMMA = GAMMA
GAMMA.DIST = GAMMA.JAKAUMA
GAMMA.INV = GAMMA.JAKAUMA.KÄÄNT
GAMMALN = GAMMALN
GAMMALN.PRECISE = GAMMALN.TARKKA
GAUSS = GAUSS
GEOMEAN = KESKIARVO.GEOM
GROWTH = KASVU
HARMEAN = KESKIARVO.HARM
HYPGEOM.DIST = HYPERGEOM_JAKAUMA
INTERCEPT = LEIKKAUSPISTE
KURT = KURT
LARGE = SUURI
LINEST = LINREGR
LOGEST = LOGREGR
LOGNORM.DIST = LOGNORM_JAKAUMA
LOGNORM.INV = LOGNORM.KÄÄNT
MAX = MAKS
MAXA = MAKSA
MAXIFS = MAKS.JOS
MEDIAN = MEDIAANI
MIN = MIN
MINA = MINA
MINIFS = MIN.JOS
MODE.MULT = MOODI.USEA
MODE.SNGL = MOODI.YKSI
NEGBINOM.DIST = BINOMI.JAKAUMA.NEG
NORM.DIST = NORMAALI.JAKAUMA
NORM.INV = NORMAALI.JAKAUMA.KÄÄNT
NORM.S.DIST = NORM_JAKAUMA.NORMIT
NORM.S.INV = NORM_JAKAUMA.KÄÄNT
PEARSON = PEARSON
PERCENTILE.EXC = PROSENTTIPISTE.ULK
PERCENTILE.INC = PROSENTTIPISTE.SIS
PERCENTRANK.EXC = PROSENTTIJÄRJESTYS.ULK
PERCENTRANK.INC = PROSENTTIJÄRJESTYS.SIS
PERMUT = PERMUTAATIO
PERMUTATIONA = PERMUTAATIOA
PHI = FII
POISSON.DIST = POISSON.JAKAUMA
PROB = TODENNÄKÖISYYS
QUARTILE.EXC = NELJÄNNES.ULK
QUARTILE.INC = NELJÄNNES.SIS
RANK.AVG = ARVON.MUKAAN.KESKIARVO
RANK.EQ = ARVON.MUKAAN.TASAN
RSQ = PEARSON.NELIÖ
SKEW = JAKAUMAN.VINOUS
SKEW.P = JAKAUMAN.VINOUS.POP
SLOPE = KULMAKERROIN
SMALL = PIENI
STANDARDIZE = NORMITA
STDEV.P = KESKIHAJONTA.P
STDEV.S = KESKIHAJONTA.S
STDEVA = KESKIHAJONTAA
STDEVPA = KESKIHAJONTAPA
STEYX = KESKIVIRHE
T.DIST = T.JAKAUMA
T.DIST.2T = T.JAKAUMA.2S
T.DIST.RT = T.JAKAUMA.OH
T.INV = T.KÄÄNT
T.INV.2T = T.KÄÄNT.2S
T.TEST = T.TESTI
TREND = SUUNTAUS
TRIMMEAN = KESKIARVO.TASATTU
VAR.P = VAR.P
VAR.S = VAR.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = WEIBULL.JAKAUMA
Z.TEST = Z.TESTI
##
## Tekstifunktiot (Text Functions)
##
BAHTTEXT = BAHTTEKSTI
CHAR = MERKKI
CLEAN = SIIVOA
CODE = KOODI
CONCAT = YHDISTÄ
DOLLAR = VALUUTTA
EXACT = VERTAA
FIND = ETSI
FIXED = KIINTEÄ
ISTHAIDIGIT = ON.THAI.NUMERO
LEFT = VASEN
LEN = PITUUS
LOWER = PIENET
MID = POIMI.TEKSTI
NUMBERSTRING = NROMERKKIJONO
NUMBERVALUE = NROARVO
PHONETIC = FONEETTINEN
PROPER = ERISNIMI
REPLACE = KORVAA
REPT = TOISTA
RIGHT = OIKEA
SEARCH = KÄY.LÄPI
SUBSTITUTE = VAIHDA
T = T
TEXT = TEKSTI
TEXTJOIN = TEKSTI.YHDISTÄ
THAIDIGIT = THAI.NUMERO
THAINUMSOUND = THAI.LUKU.ÄÄNI
THAINUMSTRING = THAI.LUKU.MERKKIJONO
THAISTRINGLENGTH = THAI.MERKKIJONON.PITUUS
TRIM = POISTA.VÄLIT
UNICHAR = UNICODEMERKKI
UNICODE = UNICODE
UPPER = ISOT
VALUE = ARVO
##
## Verkkofunktiot (Web Functions)
##
ENCODEURL = URLKOODAUS
FILTERXML = SUODATA.XML
WEBSERVICE = VERKKOPALVELU
##
## Yhteensopivuusfunktiot (Compatibility Functions)
##
BETADIST = BEETAJAKAUMA
BETAINV = BEETAJAKAUMA.KÄÄNT
BINOMDIST = BINOMIJAKAUMA
CEILING = PYÖRISTÄ.KERR.YLÖS
CHIDIST = CHIJAKAUMA
CHIINV = CHIJAKAUMA.KÄÄNT
CHITEST = CHITESTI
CONCATENATE = KETJUTA
CONFIDENCE = LUOTTAMUSVÄLI
COVAR = KOVARIANSSI
CRITBINOM = BINOMIJAKAUMA.KRIT
EXPONDIST = EKSPONENTIAALIJAKAUMA
FDIST = FJAKAUMA
FINV = FJAKAUMA.KÄÄNT
FLOOR = PYÖRISTÄ.KERR.ALAS
FORECAST = ENNUSTE
FTEST = FTESTI
GAMMADIST = GAMMAJAKAUMA
GAMMAINV = GAMMAJAKAUMA.KÄÄNT
HYPGEOMDIST = HYPERGEOM.JAKAUMA
LOGINV = LOGNORM.JAKAUMA.KÄÄNT
LOGNORMDIST = LOGNORM.JAKAUMA
MODE = MOODI
NEGBINOMDIST = BINOMIJAKAUMA.NEG
NORMDIST = NORM.JAKAUMA
NORMINV = NORM.JAKAUMA.KÄÄNT
NORMSDIST = NORM.JAKAUMA.NORMIT
NORMSINV = NORM.JAKAUMA.NORMIT.KÄÄNT
PERCENTILE = PROSENTTIPISTE
PERCENTRANK = PROSENTTIJÄRJESTYS
POISSON = POISSON
QUARTILE = NELJÄNNES
RANK = ARVON.MUKAAN
STDEV = KESKIHAJONTA
STDEVP = KESKIHAJONTAP
TDIST = TJAKAUMA
TINV = TJAKAUMA.KÄÄNT
TTEST = TTESTI
VAR = VAR
VARP = VARP
WEIBULL = WEIBULL
ZTEST = ZTESTI
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config 0000644 00000000521 15167673465 0020576 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Suomi (Finnish)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #TYHJÄ!
DIV0 = #JAKO/0!
VALUE = #ARVO!
REF = #VIITTAUS!
NAME = #NIMI?
NUM = #LUKU!
NA = #PUUTTUU!
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions 0000644 00000024740 15167673465 0021363 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Français (French)
##
############################################################
##
## Fonctions Cube (Cube Functions)
##
CUBEKPIMEMBER = MEMBREKPICUBE
CUBEMEMBER = MEMBRECUBE
CUBEMEMBERPROPERTY = PROPRIETEMEMBRECUBE
CUBERANKEDMEMBER = RANGMEMBRECUBE
CUBESET = JEUCUBE
CUBESETCOUNT = NBJEUCUBE
CUBEVALUE = VALEURCUBE
##
## Fonctions de base de données (Database Functions)
##
DAVERAGE = BDMOYENNE
DCOUNT = BDNB
DCOUNTA = BDNBVAL
DGET = BDLIRE
DMAX = BDMAX
DMIN = BDMIN
DPRODUCT = BDPRODUIT
DSTDEV = BDECARTYPE
DSTDEVP = BDECARTYPEP
DSUM = BDSOMME
DVAR = BDVAR
DVARP = BDVARP
##
## Fonctions de date et d’heure (Date & Time Functions)
##
DATE = DATE
DATEVALUE = DATEVAL
DAY = JOUR
DAYS = JOURS
DAYS360 = JOURS360
EDATE = MOIS.DECALER
EOMONTH = FIN.MOIS
HOUR = HEURE
ISOWEEKNUM = NO.SEMAINE.ISO
MINUTE = MINUTE
MONTH = MOIS
NETWORKDAYS = NB.JOURS.OUVRES
NETWORKDAYS.INTL = NB.JOURS.OUVRES.INTL
NOW = MAINTENANT
SECOND = SECONDE
TIME = TEMPS
TIMEVALUE = TEMPSVAL
TODAY = AUJOURDHUI
WEEKDAY = JOURSEM
WEEKNUM = NO.SEMAINE
WORKDAY = SERIE.JOUR.OUVRE
WORKDAY.INTL = SERIE.JOUR.OUVRE.INTL
YEAR = ANNEE
YEARFRAC = FRACTION.ANNEE
##
## Fonctions d’ingénierie (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BINDEC
BIN2HEX = BINHEX
BIN2OCT = BINOCT
BITAND = BITET
BITLSHIFT = BITDECALG
BITOR = BITOU
BITRSHIFT = BITDECALD
BITXOR = BITOUEXCLUSIF
COMPLEX = COMPLEXE
CONVERT = CONVERT
DEC2BIN = DECBIN
DEC2HEX = DECHEX
DEC2OCT = DECOCT
DELTA = DELTA
ERF = ERF
ERF.PRECISE = ERF.PRECIS
ERFC = ERFC
ERFC.PRECISE = ERFC.PRECIS
GESTEP = SUP.SEUIL
HEX2BIN = HEXBIN
HEX2DEC = HEXDEC
HEX2OCT = HEXOCT
IMABS = COMPLEXE.MODULE
IMAGINARY = COMPLEXE.IMAGINAIRE
IMARGUMENT = COMPLEXE.ARGUMENT
IMCONJUGATE = COMPLEXE.CONJUGUE
IMCOS = COMPLEXE.COS
IMCOSH = COMPLEXE.COSH
IMCOT = COMPLEXE.COT
IMCSC = COMPLEXE.CSC
IMCSCH = COMPLEXE.CSCH
IMDIV = COMPLEXE.DIV
IMEXP = COMPLEXE.EXP
IMLN = COMPLEXE.LN
IMLOG10 = COMPLEXE.LOG10
IMLOG2 = COMPLEXE.LOG2
IMPOWER = COMPLEXE.PUISSANCE
IMPRODUCT = COMPLEXE.PRODUIT
IMREAL = COMPLEXE.REEL
IMSEC = COMPLEXE.SEC
IMSECH = COMPLEXE.SECH
IMSIN = COMPLEXE.SIN
IMSINH = COMPLEXE.SINH
IMSQRT = COMPLEXE.RACINE
IMSUB = COMPLEXE.DIFFERENCE
IMSUM = COMPLEXE.SOMME
IMTAN = COMPLEXE.TAN
OCT2BIN = OCTBIN
OCT2DEC = OCTDEC
OCT2HEX = OCTHEX
##
## Fonctions financières (Financial Functions)
##
ACCRINT = INTERET.ACC
ACCRINTM = INTERET.ACC.MAT
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = NB.JOURS.COUPON.PREC
COUPDAYS = NB.JOURS.COUPONS
COUPDAYSNC = NB.JOURS.COUPON.SUIV
COUPNCD = DATE.COUPON.SUIV
COUPNUM = NB.COUPONS
COUPPCD = DATE.COUPON.PREC
CUMIPMT = CUMUL.INTER
CUMPRINC = CUMUL.PRINCPER
DB = DB
DDB = DDB
DISC = TAUX.ESCOMPTE
DOLLARDE = PRIX.DEC
DOLLARFR = PRIX.FRAC
DURATION = DUREE
EFFECT = TAUX.EFFECTIF
FV = VC
FVSCHEDULE = VC.PAIEMENTS
INTRATE = TAUX.INTERET
IPMT = INTPER
IRR = TRI
ISPMT = ISPMT
MDURATION = DUREE.MODIFIEE
MIRR = TRIM
NOMINAL = TAUX.NOMINAL
NPER = NPM
NPV = VAN
ODDFPRICE = PRIX.PCOUPON.IRREG
ODDFYIELD = REND.PCOUPON.IRREG
ODDLPRICE = PRIX.DCOUPON.IRREG
ODDLYIELD = REND.DCOUPON.IRREG
PDURATION = PDUREE
PMT = VPM
PPMT = PRINCPER
PRICE = PRIX.TITRE
PRICEDISC = VALEUR.ENCAISSEMENT
PRICEMAT = PRIX.TITRE.ECHEANCE
PV = VA
RATE = TAUX
RECEIVED = VALEUR.NOMINALE
RRI = TAUX.INT.EQUIV
SLN = AMORLIN
SYD = SYD
TBILLEQ = TAUX.ESCOMPTE.R
TBILLPRICE = PRIX.BON.TRESOR
TBILLYIELD = RENDEMENT.BON.TRESOR
VDB = VDB
XIRR = TRI.PAIEMENTS
XNPV = VAN.PAIEMENTS
YIELD = RENDEMENT.TITRE
YIELDDISC = RENDEMENT.SIMPLE
YIELDMAT = RENDEMENT.TITRE.ECHEANCE
##
## Fonctions d’information (Information Functions)
##
CELL = CELLULE
ERROR.TYPE = TYPE.ERREUR
INFO = INFORMATIONS
ISBLANK = ESTVIDE
ISERR = ESTERR
ISERROR = ESTERREUR
ISEVEN = EST.PAIR
ISFORMULA = ESTFORMULE
ISLOGICAL = ESTLOGIQUE
ISNA = ESTNA
ISNONTEXT = ESTNONTEXTE
ISNUMBER = ESTNUM
ISODD = EST.IMPAIR
ISREF = ESTREF
ISTEXT = ESTTEXTE
N = N
NA = NA
SHEET = FEUILLE
SHEETS = FEUILLES
TYPE = TYPE
##
## Fonctions logiques (Logical Functions)
##
AND = ET
FALSE = FAUX
IF = SI
IFERROR = SIERREUR
IFNA = SI.NON.DISP
IFS = SI.CONDITIONS
NOT = NON
OR = OU
SWITCH = SI.MULTIPLE
TRUE = VRAI
XOR = OUX
##
## Fonctions de recherche et de référence (Lookup & Reference Functions)
##
ADDRESS = ADRESSE
AREAS = ZONES
CHOOSE = CHOISIR
COLUMN = COLONNE
COLUMNS = COLONNES
FORMULATEXT = FORMULETEXTE
GETPIVOTDATA = LIREDONNEESTABCROISDYNAMIQUE
HLOOKUP = RECHERCHEH
HYPERLINK = LIEN_HYPERTEXTE
INDEX = INDEX
INDIRECT = INDIRECT
LOOKUP = RECHERCHE
MATCH = EQUIV
OFFSET = DECALER
ROW = LIGNE
ROWS = LIGNES
RTD = RTD
TRANSPOSE = TRANSPOSE
VLOOKUP = RECHERCHEV
*RC = LC
##
## Fonctions mathématiques et trigonométriques (Math & Trig Functions)
##
ABS = ABS
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = AGREGAT
ARABIC = CHIFFRE.ARABE
ASIN = ASIN
ASINH = ASINH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = BASE
CEILING.MATH = PLAFOND.MATH
CEILING.PRECISE = PLAFOND.PRECIS
COMBIN = COMBIN
COMBINA = COMBINA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DECIMAL
DEGREES = DEGRES
ECMA.CEILING = ECMA.PLAFOND
EVEN = PAIR
EXP = EXP
FACT = FACT
FACTDOUBLE = FACTDOUBLE
FLOOR.MATH = PLANCHER.MATH
FLOOR.PRECISE = PLANCHER.PRECIS
GCD = PGCD
INT = ENT
ISO.CEILING = ISO.PLAFOND
LCM = PPCM
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = DETERMAT
MINVERSE = INVERSEMAT
MMULT = PRODUITMAT
MOD = MOD
MROUND = ARRONDI.AU.MULTIPLE
MULTINOMIAL = MULTINOMIALE
MUNIT = MATRICE.UNITAIRE
ODD = IMPAIR
PI = PI
POWER = PUISSANCE
PRODUCT = PRODUIT
QUOTIENT = QUOTIENT
RADIANS = RADIANS
RAND = ALEA
RANDBETWEEN = ALEA.ENTRE.BORNES
ROMAN = ROMAIN
ROUND = ARRONDI
ROUNDDOWN = ARRONDI.INF
ROUNDUP = ARRONDI.SUP
SEC = SEC
SECH = SECH
SERIESSUM = SOMME.SERIES
SIGN = SIGNE
SIN = SIN
SINH = SINH
SQRT = RACINE
SQRTPI = RACINE.PI
SUBTOTAL = SOUS.TOTAL
SUM = SOMME
SUMIF = SOMME.SI
SUMIFS = SOMME.SI.ENS
SUMPRODUCT = SOMMEPROD
SUMSQ = SOMME.CARRES
SUMX2MY2 = SOMME.X2MY2
SUMX2PY2 = SOMME.X2PY2
SUMXMY2 = SOMME.XMY2
TAN = TAN
TANH = TANH
TRUNC = TRONQUE
##
## Fonctions statistiques (Statistical Functions)
##
AVEDEV = ECART.MOYEN
AVERAGE = MOYENNE
AVERAGEA = AVERAGEA
AVERAGEIF = MOYENNE.SI
AVERAGEIFS = MOYENNE.SI.ENS
BETA.DIST = LOI.BETA.N
BETA.INV = BETA.INVERSE.N
BINOM.DIST = LOI.BINOMIALE.N
BINOM.DIST.RANGE = LOI.BINOMIALE.SERIE
BINOM.INV = LOI.BINOMIALE.INVERSE
CHISQ.DIST = LOI.KHIDEUX.N
CHISQ.DIST.RT = LOI.KHIDEUX.DROITE
CHISQ.INV = LOI.KHIDEUX.INVERSE
CHISQ.INV.RT = LOI.KHIDEUX.INVERSE.DROITE
CHISQ.TEST = CHISQ.TEST
CONFIDENCE.NORM = INTERVALLE.CONFIANCE.NORMAL
CONFIDENCE.T = INTERVALLE.CONFIANCE.STUDENT
CORREL = COEFFICIENT.CORRELATION
COUNT = NB
COUNTA = NBVAL
COUNTBLANK = NB.VIDE
COUNTIF = NB.SI
COUNTIFS = NB.SI.ENS
COVARIANCE.P = COVARIANCE.PEARSON
COVARIANCE.S = COVARIANCE.STANDARD
DEVSQ = SOMME.CARRES.ECARTS
EXPON.DIST = LOI.EXPONENTIELLE.N
F.DIST = LOI.F.N
F.DIST.RT = LOI.F.DROITE
F.INV = INVERSE.LOI.F.N
F.INV.RT = INVERSE.LOI.F.DROITE
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHER.INVERSE
FORECAST.ETS = PREVISION.ETS
FORECAST.ETS.CONFINT = PREVISION.ETS.CONFINT
FORECAST.ETS.SEASONALITY = PREVISION.ETS.CARACTERESAISONNIER
FORECAST.ETS.STAT = PREVISION.ETS.STAT
FORECAST.LINEAR = PREVISION.LINEAIRE
FREQUENCY = FREQUENCE
GAMMA = GAMMA
GAMMA.DIST = LOI.GAMMA.N
GAMMA.INV = LOI.GAMMA.INVERSE.N
GAMMALN = LNGAMMA
GAMMALN.PRECISE = LNGAMMA.PRECIS
GAUSS = GAUSS
GEOMEAN = MOYENNE.GEOMETRIQUE
GROWTH = CROISSANCE
HARMEAN = MOYENNE.HARMONIQUE
HYPGEOM.DIST = LOI.HYPERGEOMETRIQUE.N
INTERCEPT = ORDONNEE.ORIGINE
KURT = KURTOSIS
LARGE = GRANDE.VALEUR
LINEST = DROITEREG
LOGEST = LOGREG
LOGNORM.DIST = LOI.LOGNORMALE.N
LOGNORM.INV = LOI.LOGNORMALE.INVERSE.N
MAX = MAX
MAXA = MAXA
MAXIFS = MAX.SI
MEDIAN = MEDIANE
MIN = MIN
MINA = MINA
MINIFS = MIN.SI
MODE.MULT = MODE.MULTIPLE
MODE.SNGL = MODE.SIMPLE
NEGBINOM.DIST = LOI.BINOMIALE.NEG.N
NORM.DIST = LOI.NORMALE.N
NORM.INV = LOI.NORMALE.INVERSE.N
NORM.S.DIST = LOI.NORMALE.STANDARD.N
NORM.S.INV = LOI.NORMALE.STANDARD.INVERSE.N
PEARSON = PEARSON
PERCENTILE.EXC = CENTILE.EXCLURE
PERCENTILE.INC = CENTILE.INCLURE
PERCENTRANK.EXC = RANG.POURCENTAGE.EXCLURE
PERCENTRANK.INC = RANG.POURCENTAGE.INCLURE
PERMUT = PERMUTATION
PERMUTATIONA = PERMUTATIONA
PHI = PHI
POISSON.DIST = LOI.POISSON.N
PROB = PROBABILITE
QUARTILE.EXC = QUARTILE.EXCLURE
QUARTILE.INC = QUARTILE.INCLURE
RANK.AVG = MOYENNE.RANG
RANK.EQ = EQUATION.RANG
RSQ = COEFFICIENT.DETERMINATION
SKEW = COEFFICIENT.ASYMETRIE
SKEW.P = COEFFICIENT.ASYMETRIE.P
SLOPE = PENTE
SMALL = PETITE.VALEUR
STANDARDIZE = CENTREE.REDUITE
STDEV.P = ECARTYPE.PEARSON
STDEV.S = ECARTYPE.STANDARD
STDEVA = STDEVA
STDEVPA = STDEVPA
STEYX = ERREUR.TYPE.XY
T.DIST = LOI.STUDENT.N
T.DIST.2T = LOI.STUDENT.BILATERALE
T.DIST.RT = LOI.STUDENT.DROITE
T.INV = LOI.STUDENT.INVERSE.N
T.INV.2T = LOI.STUDENT.INVERSE.BILATERALE
T.TEST = T.TEST
TREND = TENDANCE
TRIMMEAN = MOYENNE.REDUITE
VAR.P = VAR.P.N
VAR.S = VAR.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = LOI.WEIBULL.N
Z.TEST = Z.TEST
##
## Fonctions de texte (Text Functions)
##
BAHTTEXT = BAHTTEXT
CHAR = CAR
CLEAN = EPURAGE
CODE = CODE
CONCAT = CONCAT
DOLLAR = DEVISE
EXACT = EXACT
FIND = TROUVE
FIXED = CTXT
LEFT = GAUCHE
LEN = NBCAR
LOWER = MINUSCULE
MID = STXT
NUMBERVALUE = VALEURNOMBRE
PHONETIC = PHONETIQUE
PROPER = NOMPROPRE
REPLACE = REMPLACER
REPT = REPT
RIGHT = DROITE
SEARCH = CHERCHE
SUBSTITUTE = SUBSTITUE
T = T
TEXT = TEXTE
TEXTJOIN = JOINDRE.TEXTE
TRIM = SUPPRESPACE
UNICHAR = UNICAR
UNICODE = UNICODE
UPPER = MAJUSCULE
VALUE = CNUM
##
## Fonctions web (Web Functions)
##
ENCODEURL = URLENCODAGE
FILTERXML = FILTRE.XML
WEBSERVICE = SERVICEWEB
##
## Fonctions de compatibilité (Compatibility Functions)
##
BETADIST = LOI.BETA
BETAINV = BETA.INVERSE
BINOMDIST = LOI.BINOMIALE
CEILING = PLAFOND
CHIDIST = LOI.KHIDEUX
CHIINV = KHIDEUX.INVERSE
CHITEST = TEST.KHIDEUX
CONCATENATE = CONCATENER
CONFIDENCE = INTERVALLE.CONFIANCE
COVAR = COVARIANCE
CRITBINOM = CRITERE.LOI.BINOMIALE
EXPONDIST = LOI.EXPONENTIELLE
FDIST = LOI.F
FINV = INVERSE.LOI.F
FLOOR = PLANCHER
FORECAST = PREVISION
FTEST = TEST.F
GAMMADIST = LOI.GAMMA
GAMMAINV = LOI.GAMMA.INVERSE
HYPGEOMDIST = LOI.HYPERGEOMETRIQUE
LOGINV = LOI.LOGNORMALE.INVERSE
LOGNORMDIST = LOI.LOGNORMALE
MODE = MODE
NEGBINOMDIST = LOI.BINOMIALE.NEG
NORMDIST = LOI.NORMALE
NORMINV = LOI.NORMALE.INVERSE
NORMSDIST = LOI.NORMALE.STANDARD
NORMSINV = LOI.NORMALE.STANDARD.INVERSE
PERCENTILE = CENTILE
PERCENTRANK = RANG.POURCENTAGE
POISSON = LOI.POISSON
QUARTILE = QUARTILE
RANK = RANG
STDEV = ECARTYPE
STDEVP = ECARTYPEP
TDIST = LOI.STUDENT
TINV = LOI.STUDENT.INVERSE
TTEST = TEST.STUDENT
VAR = VAR
VARP = VAR.P
WEIBULL = LOI.WEIBULL
ZTEST = TEST.Z
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config 0000644 00000000460 15167673465 0020611 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Français (French)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #NUL!
DIV0
VALUE = #VALEUR!
REF
NAME = #NOM?
NUM = #NOMBRE!
NA
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions 0000644 00000023633 15167673465 0021344 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Deutsch (German)
##
############################################################
##
## Cubefunktionen (Cube Functions)
##
CUBEKPIMEMBER = CUBEKPIELEMENT
CUBEMEMBER = CUBEELEMENT
CUBEMEMBERPROPERTY = CUBEELEMENTEIGENSCHAFT
CUBERANKEDMEMBER = CUBERANGELEMENT
CUBESET = CUBEMENGE
CUBESETCOUNT = CUBEMENGENANZAHL
CUBEVALUE = CUBEWERT
##
## Datenbankfunktionen (Database Functions)
##
DAVERAGE = DBMITTELWERT
DCOUNT = DBANZAHL
DCOUNTA = DBANZAHL2
DGET = DBAUSZUG
DMAX = DBMAX
DMIN = DBMIN
DPRODUCT = DBPRODUKT
DSTDEV = DBSTDABW
DSTDEVP = DBSTDABWN
DSUM = DBSUMME
DVAR = DBVARIANZ
DVARP = DBVARIANZEN
##
## Datums- und Uhrzeitfunktionen (Date & Time Functions)
##
DATE = DATUM
DATEVALUE = DATWERT
DAY = TAG
DAYS = TAGE
DAYS360 = TAGE360
EDATE = EDATUM
EOMONTH = MONATSENDE
HOUR = STUNDE
ISOWEEKNUM = ISOKALENDERWOCHE
MINUTE = MINUTE
MONTH = MONAT
NETWORKDAYS = NETTOARBEITSTAGE
NETWORKDAYS.INTL = NETTOARBEITSTAGE.INTL
NOW = JETZT
SECOND = SEKUNDE
THAIDAYOFWEEK = THAIWOCHENTAG
THAIMONTHOFYEAR = THAIMONATDESJAHRES
THAIYEAR = THAIJAHR
TIME = ZEIT
TIMEVALUE = ZEITWERT
TODAY = HEUTE
WEEKDAY = WOCHENTAG
WEEKNUM = KALENDERWOCHE
WORKDAY = ARBEITSTAG
WORKDAY.INTL = ARBEITSTAG.INTL
YEAR = JAHR
YEARFRAC = BRTEILJAHRE
##
## Technische Funktionen (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BININDEZ
BIN2HEX = BININHEX
BIN2OCT = BININOKT
BITAND = BITUND
BITLSHIFT = BITLVERSCHIEB
BITOR = BITODER
BITRSHIFT = BITRVERSCHIEB
BITXOR = BITXODER
COMPLEX = KOMPLEXE
CONVERT = UMWANDELN
DEC2BIN = DEZINBIN
DEC2HEX = DEZINHEX
DEC2OCT = DEZINOKT
DELTA = DELTA
ERF = GAUSSFEHLER
ERF.PRECISE = GAUSSF.GENAU
ERFC = GAUSSFKOMPL
ERFC.PRECISE = GAUSSFKOMPL.GENAU
GESTEP = GGANZZAHL
HEX2BIN = HEXINBIN
HEX2DEC = HEXINDEZ
HEX2OCT = HEXINOKT
IMABS = IMABS
IMAGINARY = IMAGINÄRTEIL
IMARGUMENT = IMARGUMENT
IMCONJUGATE = IMKONJUGIERTE
IMCOS = IMCOS
IMCOSH = IMCOSHYP
IMCOT = IMCOT
IMCSC = IMCOSEC
IMCSCH = IMCOSECHYP
IMDIV = IMDIV
IMEXP = IMEXP
IMLN = IMLN
IMLOG10 = IMLOG10
IMLOG2 = IMLOG2
IMPOWER = IMAPOTENZ
IMPRODUCT = IMPRODUKT
IMREAL = IMREALTEIL
IMSEC = IMSEC
IMSECH = IMSECHYP
IMSIN = IMSIN
IMSINH = IMSINHYP
IMSQRT = IMWURZEL
IMSUB = IMSUB
IMSUM = IMSUMME
IMTAN = IMTAN
OCT2BIN = OKTINBIN
OCT2DEC = OKTINDEZ
OCT2HEX = OKTINHEX
##
## Finanzmathematische Funktionen (Financial Functions)
##
ACCRINT = AUFGELZINS
ACCRINTM = AUFGELZINSF
AMORDEGRC = AMORDEGRK
AMORLINC = AMORLINEARK
COUPDAYBS = ZINSTERMTAGVA
COUPDAYS = ZINSTERMTAGE
COUPDAYSNC = ZINSTERMTAGNZ
COUPNCD = ZINSTERMNZ
COUPNUM = ZINSTERMZAHL
COUPPCD = ZINSTERMVZ
CUMIPMT = KUMZINSZ
CUMPRINC = KUMKAPITAL
DB = GDA2
DDB = GDA
DISC = DISAGIO
DOLLARDE = NOTIERUNGDEZ
DOLLARFR = NOTIERUNGBRU
DURATION = DURATION
EFFECT = EFFEKTIV
FV = ZW
FVSCHEDULE = ZW2
INTRATE = ZINSSATZ
IPMT = ZINSZ
IRR = IKV
ISPMT = ISPMT
MDURATION = MDURATION
MIRR = QIKV
NOMINAL = NOMINAL
NPER = ZZR
NPV = NBW
ODDFPRICE = UNREGER.KURS
ODDFYIELD = UNREGER.REND
ODDLPRICE = UNREGLE.KURS
ODDLYIELD = UNREGLE.REND
PDURATION = PDURATION
PMT = RMZ
PPMT = KAPZ
PRICE = KURS
PRICEDISC = KURSDISAGIO
PRICEMAT = KURSFÄLLIG
PV = BW
RATE = ZINS
RECEIVED = AUSZAHLUNG
RRI = ZSATZINVEST
SLN = LIA
SYD = DIA
TBILLEQ = TBILLÄQUIV
TBILLPRICE = TBILLKURS
TBILLYIELD = TBILLRENDITE
VDB = VDB
XIRR = XINTZINSFUSS
XNPV = XKAPITALWERT
YIELD = RENDITE
YIELDDISC = RENDITEDIS
YIELDMAT = RENDITEFÄLL
##
## Informationsfunktionen (Information Functions)
##
CELL = ZELLE
ERROR.TYPE = FEHLER.TYP
INFO = INFO
ISBLANK = ISTLEER
ISERR = ISTFEHL
ISERROR = ISTFEHLER
ISEVEN = ISTGERADE
ISFORMULA = ISTFORMEL
ISLOGICAL = ISTLOG
ISNA = ISTNV
ISNONTEXT = ISTKTEXT
ISNUMBER = ISTZAHL
ISODD = ISTUNGERADE
ISREF = ISTBEZUG
ISTEXT = ISTTEXT
N = N
NA = NV
SHEET = BLATT
SHEETS = BLÄTTER
TYPE = TYP
##
## Logische Funktionen (Logical Functions)
##
AND = UND
FALSE = FALSCH
IF = WENN
IFERROR = WENNFEHLER
IFNA = WENNNV
IFS = WENNS
NOT = NICHT
OR = ODER
SWITCH = ERSTERWERT
TRUE = WAHR
XOR = XODER
##
## Nachschlage- und Verweisfunktionen (Lookup & Reference Functions)
##
ADDRESS = ADRESSE
AREAS = BEREICHE
CHOOSE = WAHL
COLUMN = SPALTE
COLUMNS = SPALTEN
FORMULATEXT = FORMELTEXT
GETPIVOTDATA = PIVOTDATENZUORDNEN
HLOOKUP = WVERWEIS
HYPERLINK = HYPERLINK
INDEX = INDEX
INDIRECT = INDIREKT
LOOKUP = VERWEIS
MATCH = VERGLEICH
OFFSET = BEREICH.VERSCHIEBEN
ROW = ZEILE
ROWS = ZEILEN
RTD = RTD
TRANSPOSE = MTRANS
VLOOKUP = SVERWEIS
*RC = ZS
##
## Mathematische und trigonometrische Funktionen (Math & Trig Functions)
##
ABS = ABS
ACOS = ARCCOS
ACOSH = ARCCOSHYP
ACOT = ARCCOT
ACOTH = ARCCOTHYP
AGGREGATE = AGGREGAT
ARABIC = ARABISCH
ASIN = ARCSIN
ASINH = ARCSINHYP
ATAN = ARCTAN
ATAN2 = ARCTAN2
ATANH = ARCTANHYP
BASE = BASIS
CEILING.MATH = OBERGRENZE.MATHEMATIK
CEILING.PRECISE = OBERGRENZE.GENAU
COMBIN = KOMBINATIONEN
COMBINA = KOMBINATIONEN2
COS = COS
COSH = COSHYP
COT = COT
COTH = COTHYP
CSC = COSEC
CSCH = COSECHYP
DECIMAL = DEZIMAL
DEGREES = GRAD
ECMA.CEILING = ECMA.OBERGRENZE
EVEN = GERADE
EXP = EXP
FACT = FAKULTÄT
FACTDOUBLE = ZWEIFAKULTÄT
FLOOR.MATH = UNTERGRENZE.MATHEMATIK
FLOOR.PRECISE = UNTERGRENZE.GENAU
GCD = GGT
INT = GANZZAHL
ISO.CEILING = ISO.OBERGRENZE
LCM = KGV
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MDET
MINVERSE = MINV
MMULT = MMULT
MOD = REST
MROUND = VRUNDEN
MULTINOMIAL = POLYNOMIAL
MUNIT = MEINHEIT
ODD = UNGERADE
PI = PI
POWER = POTENZ
PRODUCT = PRODUKT
QUOTIENT = QUOTIENT
RADIANS = BOGENMASS
RAND = ZUFALLSZAHL
RANDBETWEEN = ZUFALLSBEREICH
ROMAN = RÖMISCH
ROUND = RUNDEN
ROUNDBAHTDOWN = RUNDBAHTNED
ROUNDBAHTUP = BAHTAUFRUNDEN
ROUNDDOWN = ABRUNDEN
ROUNDUP = AUFRUNDEN
SEC = SEC
SECH = SECHYP
SERIESSUM = POTENZREIHE
SIGN = VORZEICHEN
SIN = SIN
SINH = SINHYP
SQRT = WURZEL
SQRTPI = WURZELPI
SUBTOTAL = TEILERGEBNIS
SUM = SUMME
SUMIF = SUMMEWENN
SUMIFS = SUMMEWENNS
SUMPRODUCT = SUMMENPRODUKT
SUMSQ = QUADRATESUMME
SUMX2MY2 = SUMMEX2MY2
SUMX2PY2 = SUMMEX2PY2
SUMXMY2 = SUMMEXMY2
TAN = TAN
TANH = TANHYP
TRUNC = KÜRZEN
##
## Statistische Funktionen (Statistical Functions)
##
AVEDEV = MITTELABW
AVERAGE = MITTELWERT
AVERAGEA = MITTELWERTA
AVERAGEIF = MITTELWERTWENN
AVERAGEIFS = MITTELWERTWENNS
BETA.DIST = BETA.VERT
BETA.INV = BETA.INV
BINOM.DIST = BINOM.VERT
BINOM.DIST.RANGE = BINOM.VERT.BEREICH
BINOM.INV = BINOM.INV
CHISQ.DIST = CHIQU.VERT
CHISQ.DIST.RT = CHIQU.VERT.RE
CHISQ.INV = CHIQU.INV
CHISQ.INV.RT = CHIQU.INV.RE
CHISQ.TEST = CHIQU.TEST
CONFIDENCE.NORM = KONFIDENZ.NORM
CONFIDENCE.T = KONFIDENZ.T
CORREL = KORREL
COUNT = ANZAHL
COUNTA = ANZAHL2
COUNTBLANK = ANZAHLLEEREZELLEN
COUNTIF = ZÄHLENWENN
COUNTIFS = ZÄHLENWENNS
COVARIANCE.P = KOVARIANZ.P
COVARIANCE.S = KOVARIANZ.S
DEVSQ = SUMQUADABW
EXPON.DIST = EXPON.VERT
F.DIST = F.VERT
F.DIST.RT = F.VERT.RE
F.INV = F.INV
F.INV.RT = F.INV.RE
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = PROGNOSE.ETS
FORECAST.ETS.CONFINT = PROGNOSE.ETS.KONFINT
FORECAST.ETS.SEASONALITY = PROGNOSE.ETS.SAISONALITÄT
FORECAST.ETS.STAT = PROGNOSE.ETS.STAT
FORECAST.LINEAR = PROGNOSE.LINEAR
FREQUENCY = HÄUFIGKEIT
GAMMA = GAMMA
GAMMA.DIST = GAMMA.VERT
GAMMA.INV = GAMMA.INV
GAMMALN = GAMMALN
GAMMALN.PRECISE = GAMMALN.GENAU
GAUSS = GAUSS
GEOMEAN = GEOMITTEL
GROWTH = VARIATION
HARMEAN = HARMITTEL
HYPGEOM.DIST = HYPGEOM.VERT
INTERCEPT = ACHSENABSCHNITT
KURT = KURT
LARGE = KGRÖSSTE
LINEST = RGP
LOGEST = RKP
LOGNORM.DIST = LOGNORM.VERT
LOGNORM.INV = LOGNORM.INV
MAX = MAX
MAXA = MAXA
MAXIFS = MAXWENNS
MEDIAN = MEDIAN
MIN = MIN
MINA = MINA
MINIFS = MINWENNS
MODE.MULT = MODUS.VIELF
MODE.SNGL = MODUS.EINF
NEGBINOM.DIST = NEGBINOM.VERT
NORM.DIST = NORM.VERT
NORM.INV = NORM.INV
NORM.S.DIST = NORM.S.VERT
NORM.S.INV = NORM.S.INV
PEARSON = PEARSON
PERCENTILE.EXC = QUANTIL.EXKL
PERCENTILE.INC = QUANTIL.INKL
PERCENTRANK.EXC = QUANTILSRANG.EXKL
PERCENTRANK.INC = QUANTILSRANG.INKL
PERMUT = VARIATIONEN
PERMUTATIONA = VARIATIONEN2
PHI = PHI
POISSON.DIST = POISSON.VERT
PROB = WAHRSCHBEREICH
QUARTILE.EXC = QUARTILE.EXKL
QUARTILE.INC = QUARTILE.INKL
RANK.AVG = RANG.MITTELW
RANK.EQ = RANG.GLEICH
RSQ = BESTIMMTHEITSMASS
SKEW = SCHIEFE
SKEW.P = SCHIEFE.P
SLOPE = STEIGUNG
SMALL = KKLEINSTE
STANDARDIZE = STANDARDISIERUNG
STDEV.P = STABW.N
STDEV.S = STABW.S
STDEVA = STABWA
STDEVPA = STABWNA
STEYX = STFEHLERYX
T.DIST = T.VERT
T.DIST.2T = T.VERT.2S
T.DIST.RT = T.VERT.RE
T.INV = T.INV
T.INV.2T = T.INV.2S
T.TEST = T.TEST
TREND = TREND
TRIMMEAN = GESTUTZTMITTEL
VAR.P = VAR.P
VAR.S = VAR.S
VARA = VARIANZA
VARPA = VARIANZENA
WEIBULL.DIST = WEIBULL.VERT
Z.TEST = G.TEST
##
## Textfunktionen (Text Functions)
##
BAHTTEXT = BAHTTEXT
CHAR = ZEICHEN
CLEAN = SÄUBERN
CODE = CODE
CONCAT = TEXTKETTE
DOLLAR = DM
EXACT = IDENTISCH
FIND = FINDEN
FIXED = FEST
ISTHAIDIGIT = ISTTHAIZAHLENWORT
LEFT = LINKS
LEN = LÄNGE
LOWER = KLEIN
MID = TEIL
NUMBERVALUE = ZAHLENWERT
PROPER = GROSS2
REPLACE = ERSETZEN
REPT = WIEDERHOLEN
RIGHT = RECHTS
SEARCH = SUCHEN
SUBSTITUTE = WECHSELN
T = T
TEXT = TEXT
TEXTJOIN = TEXTVERKETTEN
THAIDIGIT = THAIZAHLENWORT
THAINUMSOUND = THAIZAHLSOUND
THAINUMSTRING = THAILANDSKNUMSTRENG
THAISTRINGLENGTH = THAIZEICHENFOLGENLÄNGE
TRIM = GLÄTTEN
UNICHAR = UNIZEICHEN
UNICODE = UNICODE
UPPER = GROSS
VALUE = WERT
##
## Webfunktionen (Web Functions)
##
ENCODEURL = URLCODIEREN
FILTERXML = XMLFILTERN
WEBSERVICE = WEBDIENST
##
## Kompatibilitätsfunktionen (Compatibility Functions)
##
BETADIST = BETAVERT
BETAINV = BETAINV
BINOMDIST = BINOMVERT
CEILING = OBERGRENZE
CHIDIST = CHIVERT
CHIINV = CHIINV
CHITEST = CHITEST
CONCATENATE = VERKETTEN
CONFIDENCE = KONFIDENZ
COVAR = KOVAR
CRITBINOM = KRITBINOM
EXPONDIST = EXPONVERT
FDIST = FVERT
FINV = FINV
FLOOR = UNTERGRENZE
FORECAST = SCHÄTZER
FTEST = FTEST
GAMMADIST = GAMMAVERT
GAMMAINV = GAMMAINV
HYPGEOMDIST = HYPGEOMVERT
LOGINV = LOGINV
LOGNORMDIST = LOGNORMVERT
MODE = MODALWERT
NEGBINOMDIST = NEGBINOMVERT
NORMDIST = NORMVERT
NORMINV = NORMINV
NORMSDIST = STANDNORMVERT
NORMSINV = STANDNORMINV
PERCENTILE = QUANTIL
PERCENTRANK = QUANTILSRANG
POISSON = POISSON
QUARTILE = QUARTILE
RANK = RANG
STDEV = STABW
STDEVP = STABWN
TDIST = TVERT
TINV = TINV
TTEST = TTEST
VAR = VARIANZ
VARP = VARIANZEN
WEIBULL = WEIBULL
ZTEST = GTEST
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config 0000644 00000000452 15167673465 0020573 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Deutsch (German)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL
DIV0
VALUE = #WERT!
REF = #BEZUG!
NAME
NUM = #ZAHL!
NA = #NV
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/functions 0000644 00000024751 15167673465 0021355 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Norsk Bokmål (Norwegian Bokmål)
##
############################################################
##
## Kubefunksjoner (Cube Functions)
##
CUBEKPIMEMBER = KUBEKPIMEDLEM
CUBEMEMBER = KUBEMEDLEM
CUBEMEMBERPROPERTY = KUBEMEDLEMEGENSKAP
CUBERANKEDMEMBER = KUBERANGERTMEDLEM
CUBESET = KUBESETT
CUBESETCOUNT = KUBESETTANTALL
CUBEVALUE = KUBEVERDI
##
## Databasefunksjoner (Database Functions)
##
DAVERAGE = DGJENNOMSNITT
DCOUNT = DANTALL
DCOUNTA = DANTALLA
DGET = DHENT
DMAX = DMAKS
DMIN = DMIN
DPRODUCT = DPRODUKT
DSTDEV = DSTDAV
DSTDEVP = DSTDAVP
DSUM = DSUMMER
DVAR = DVARIANS
DVARP = DVARIANSP
##
## Dato- og tidsfunksjoner (Date & Time Functions)
##
DATE = DATO
DATEDIF = DATODIFF
DATESTRING = DATOSTRENG
DATEVALUE = DATOVERDI
DAY = DAG
DAYS = DAGER
DAYS360 = DAGER360
EDATE = DAG.ETTER
EOMONTH = MÅNEDSSLUTT
HOUR = TIME
ISOWEEKNUM = ISOUKENR
MINUTE = MINUTT
MONTH = MÅNED
NETWORKDAYS = NETT.ARBEIDSDAGER
NETWORKDAYS.INTL = NETT.ARBEIDSDAGER.INTL
NOW = NÅ
SECOND = SEKUND
THAIDAYOFWEEK = THAIUKEDAG
THAIMONTHOFYEAR = THAIMÅNED
THAIYEAR = THAIÅR
TIME = TID
TIMEVALUE = TIDSVERDI
TODAY = IDAG
WEEKDAY = UKEDAG
WEEKNUM = UKENR
WORKDAY = ARBEIDSDAG
WORKDAY.INTL = ARBEIDSDAG.INTL
YEAR = ÅR
YEARFRAC = ÅRDEL
##
## Tekniske funksjoner (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BINTILDES
BIN2HEX = BINTILHEKS
BIN2OCT = BINTILOKT
BITAND = BITOG
BITLSHIFT = BITVFORSKYV
BITOR = BITELLER
BITRSHIFT = BITHFORSKYV
BITXOR = BITEKSKLUSIVELLER
COMPLEX = KOMPLEKS
CONVERT = KONVERTER
DEC2BIN = DESTILBIN
DEC2HEX = DESTILHEKS
DEC2OCT = DESTILOKT
DELTA = DELTA
ERF = FEILF
ERF.PRECISE = FEILF.PRESIS
ERFC = FEILFK
ERFC.PRECISE = FEILFK.PRESIS
GESTEP = GRENSEVERDI
HEX2BIN = HEKSTILBIN
HEX2DEC = HEKSTILDES
HEX2OCT = HEKSTILOKT
IMABS = IMABS
IMAGINARY = IMAGINÆR
IMARGUMENT = IMARGUMENT
IMCONJUGATE = IMKONJUGERT
IMCOS = IMCOS
IMCOSH = IMCOSH
IMCOT = IMCOT
IMCSC = IMCSC
IMCSCH = IMCSCH
IMDIV = IMDIV
IMEXP = IMEKSP
IMLN = IMLN
IMLOG10 = IMLOG10
IMLOG2 = IMLOG2
IMPOWER = IMOPPHØY
IMPRODUCT = IMPRODUKT
IMREAL = IMREELL
IMSEC = IMSEC
IMSECH = IMSECH
IMSIN = IMSIN
IMSINH = IMSINH
IMSQRT = IMROT
IMSUB = IMSUB
IMSUM = IMSUMMER
IMTAN = IMTAN
OCT2BIN = OKTTILBIN
OCT2DEC = OKTTILDES
OCT2HEX = OKTTILHEKS
##
## Økonomiske funksjoner (Financial Functions)
##
ACCRINT = PÅLØPT.PERIODISK.RENTE
ACCRINTM = PÅLØPT.FORFALLSRENTE
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = OBLIG.DAGER.FF
COUPDAYS = OBLIG.DAGER
COUPDAYSNC = OBLIG.DAGER.NF
COUPNCD = OBLIG.DAGER.EF
COUPNUM = OBLIG.ANTALL
COUPPCD = OBLIG.DAG.FORRIGE
CUMIPMT = SAMLET.RENTE
CUMPRINC = SAMLET.HOVEDSTOL
DB = DAVSKR
DDB = DEGRAVS
DISC = DISKONTERT
DOLLARDE = DOLLARDE
DOLLARFR = DOLLARBR
DURATION = VARIGHET
EFFECT = EFFEKTIV.RENTE
FV = SLUTTVERDI
FVSCHEDULE = SVPLAN
INTRATE = RENTESATS
IPMT = RAVDRAG
IRR = IR
ISPMT = ER.AVDRAG
MDURATION = MVARIGHET
MIRR = MODIR
NOMINAL = NOMINELL
NPER = PERIODER
NPV = NNV
ODDFPRICE = AVVIKFP.PRIS
ODDFYIELD = AVVIKFP.AVKASTNING
ODDLPRICE = AVVIKSP.PRIS
ODDLYIELD = AVVIKSP.AVKASTNING
PDURATION = PVARIGHET
PMT = AVDRAG
PPMT = AMORT
PRICE = PRIS
PRICEDISC = PRIS.DISKONTERT
PRICEMAT = PRIS.FORFALL
PV = NÅVERDI
RATE = RENTE
RECEIVED = MOTTATT.AVKAST
RRI = REALISERT.AVKASTNING
SLN = LINAVS
SYD = ÅRSAVS
TBILLEQ = TBILLEKV
TBILLPRICE = TBILLPRIS
TBILLYIELD = TBILLAVKASTNING
VDB = VERDIAVS
XIRR = XIR
XNPV = XNNV
YIELD = AVKAST
YIELDDISC = AVKAST.DISKONTERT
YIELDMAT = AVKAST.FORFALL
##
## Informasjonsfunksjoner (Information Functions)
##
CELL = CELLE
ERROR.TYPE = FEIL.TYPE
INFO = INFO
ISBLANK = ERTOM
ISERR = ERF
ISERROR = ERFEIL
ISEVEN = ERPARTALL
ISFORMULA = ERFORMEL
ISLOGICAL = ERLOGISK
ISNA = ERIT
ISNONTEXT = ERIKKETEKST
ISNUMBER = ERTALL
ISODD = ERODDE
ISREF = ERREF
ISTEXT = ERTEKST
N = N
NA = IT
SHEET = ARK
SHEETS = ANTALL.ARK
TYPE = VERDITYPE
##
## Logiske funksjoner (Logical Functions)
##
AND = OG
FALSE = USANN
IF = HVIS
IFERROR = HVISFEIL
IFNA = HVIS.IT
IFS = HVIS.SETT
NOT = IKKE
OR = ELLER
SWITCH = BRYTER
TRUE = SANN
XOR = EKSKLUSIVELLER
##
## Oppslag- og referansefunksjoner (Lookup & Reference Functions)
##
ADDRESS = ADRESSE
AREAS = OMRÅDER
CHOOSE = VELG
COLUMN = KOLONNE
COLUMNS = KOLONNER
FORMULATEXT = FORMELTEKST
GETPIVOTDATA = HENTPIVOTDATA
HLOOKUP = FINN.KOLONNE
HYPERLINK = HYPERKOBLING
INDEX = INDEKS
INDIRECT = INDIREKTE
LOOKUP = SLÅ.OPP
MATCH = SAMMENLIGNE
OFFSET = FORSKYVNING
ROW = RAD
ROWS = RADER
RTD = RTD
TRANSPOSE = TRANSPONER
VLOOKUP = FINN.RAD
*RC = RK
##
## Matematikk- og trigonometrifunksjoner (Math & Trig Functions)
##
ABS = ABS
ACOS = ARCCOS
ACOSH = ARCCOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = MENGDE
ARABIC = ARABISK
ASIN = ARCSIN
ASINH = ARCSINH
ATAN = ARCTAN
ATAN2 = ARCTAN2
ATANH = ARCTANH
BASE = GRUNNTALL
CEILING.MATH = AVRUND.GJELDENDE.MULTIPLUM.OPP.MATEMATISK
CEILING.PRECISE = AVRUND.GJELDENDE.MULTIPLUM.PRESIS
COMBIN = KOMBINASJON
COMBINA = KOMBINASJONA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DESIMAL
DEGREES = GRADER
ECMA.CEILING = ECMA.AVRUND.GJELDENDE.MULTIPLUM
EVEN = AVRUND.TIL.PARTALL
EXP = EKSP
FACT = FAKULTET
FACTDOUBLE = DOBBELFAKT
FLOOR.MATH = AVRUND.GJELDENDE.MULTIPLUM.NED.MATEMATISK
FLOOR.PRECISE = AVRUND.GJELDENDE.MULTIPLUM.NED.PRESIS
GCD = SFF
INT = HELTALL
ISO.CEILING = ISO.AVRUND.GJELDENDE.MULTIPLUM
LCM = MFM
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MDETERM
MINVERSE = MINVERS
MMULT = MMULT
MOD = REST
MROUND = MRUND
MULTINOMIAL = MULTINOMINELL
MUNIT = MENHET
ODD = AVRUND.TIL.ODDETALL
PI = PI
POWER = OPPHØYD.I
PRODUCT = PRODUKT
QUOTIENT = KVOTIENT
RADIANS = RADIANER
RAND = TILFELDIG
RANDBETWEEN = TILFELDIGMELLOM
ROMAN = ROMERTALL
ROUND = AVRUND
ROUNDBAHTDOWN = RUNDAVBAHTNEDOVER
ROUNDBAHTUP = RUNDAVBAHTOPPOVER
ROUNDDOWN = AVRUND.NED
ROUNDUP = AVRUND.OPP
SEC = SEC
SECH = SECH
SERIESSUM = SUMMER.REKKE
SIGN = FORTEGN
SIN = SIN
SINH = SINH
SQRT = ROT
SQRTPI = ROTPI
SUBTOTAL = DELSUM
SUM = SUMMER
SUMIF = SUMMERHVIS
SUMIFS = SUMMER.HVIS.SETT
SUMPRODUCT = SUMMERPRODUKT
SUMSQ = SUMMERKVADRAT
SUMX2MY2 = SUMMERX2MY2
SUMX2PY2 = SUMMERX2PY2
SUMXMY2 = SUMMERXMY2
TAN = TAN
TANH = TANH
TRUNC = AVKORT
##
## Statistiske funksjoner (Statistical Functions)
##
AVEDEV = GJENNOMSNITTSAVVIK
AVERAGE = GJENNOMSNITT
AVERAGEA = GJENNOMSNITTA
AVERAGEIF = GJENNOMSNITTHVIS
AVERAGEIFS = GJENNOMSNITT.HVIS.SETT
BETA.DIST = BETA.FORDELING.N
BETA.INV = BETA.INV
BINOM.DIST = BINOM.FORDELING.N
BINOM.DIST.RANGE = BINOM.FORDELING.OMRÅDE
BINOM.INV = BINOM.INV
CHISQ.DIST = KJIKVADRAT.FORDELING
CHISQ.DIST.RT = KJIKVADRAT.FORDELING.H
CHISQ.INV = KJIKVADRAT.INV
CHISQ.INV.RT = KJIKVADRAT.INV.H
CHISQ.TEST = KJIKVADRAT.TEST
CONFIDENCE.NORM = KONFIDENS.NORM
CONFIDENCE.T = KONFIDENS.T
CORREL = KORRELASJON
COUNT = ANTALL
COUNTA = ANTALLA
COUNTBLANK = TELLBLANKE
COUNTIF = ANTALL.HVIS
COUNTIFS = ANTALL.HVIS.SETT
COVARIANCE.P = KOVARIANS.P
COVARIANCE.S = KOVARIANS.S
DEVSQ = AVVIK.KVADRERT
EXPON.DIST = EKSP.FORDELING.N
F.DIST = F.FORDELING
F.DIST.RT = F.FORDELING.H
F.INV = F.INV
F.INV.RT = F.INV.H
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = PROGNOSE.ETS
FORECAST.ETS.CONFINT = PROGNOSE.ETS.CONFINT
FORECAST.ETS.SEASONALITY = PROGNOSE.ETS.SESONGAVHENGIGHET
FORECAST.ETS.STAT = PROGNOSE.ETS.STAT
FORECAST.LINEAR = PROGNOSE.LINEÆR
FREQUENCY = FREKVENS
GAMMA = GAMMA
GAMMA.DIST = GAMMA.FORDELING
GAMMA.INV = GAMMA.INV
GAMMALN = GAMMALN
GAMMALN.PRECISE = GAMMALN.PRESIS
GAUSS = GAUSS
GEOMEAN = GJENNOMSNITT.GEOMETRISK
GROWTH = VEKST
HARMEAN = GJENNOMSNITT.HARMONISK
HYPGEOM.DIST = HYPGEOM.FORDELING.N
INTERCEPT = SKJÆRINGSPUNKT
KURT = KURT
LARGE = N.STØRST
LINEST = RETTLINJE
LOGEST = KURVE
LOGNORM.DIST = LOGNORM.FORDELING
LOGNORM.INV = LOGNORM.INV
MAX = STØRST
MAXA = MAKSA
MAXIFS = MAKS.HVIS.SETT
MEDIAN = MEDIAN
MIN = MIN
MINA = MINA
MINIFS = MIN.HVIS.SETT
MODE.MULT = MODUS.MULT
MODE.SNGL = MODUS.SNGL
NEGBINOM.DIST = NEGBINOM.FORDELING.N
NORM.DIST = NORM.FORDELING
NORM.INV = NORM.INV
NORM.S.DIST = NORM.S.FORDELING
NORM.S.INV = NORM.S.INV
PEARSON = PEARSON
PERCENTILE.EXC = PERSENTIL.EKS
PERCENTILE.INC = PERSENTIL.INK
PERCENTRANK.EXC = PROSENTDEL.EKS
PERCENTRANK.INC = PROSENTDEL.INK
PERMUT = PERMUTER
PERMUTATIONA = PERMUTASJONA
PHI = PHI
POISSON.DIST = POISSON.FORDELING
PROB = SANNSYNLIG
QUARTILE.EXC = KVARTIL.EKS
QUARTILE.INC = KVARTIL.INK
RANK.AVG = RANG.GJSN
RANK.EQ = RANG.EKV
RSQ = RKVADRAT
SKEW = SKJEVFORDELING
SKEW.P = SKJEVFORDELING.P
SLOPE = STIGNINGSTALL
SMALL = N.MINST
STANDARDIZE = NORMALISER
STDEV.P = STDAV.P
STDEV.S = STDAV.S
STDEVA = STDAVVIKA
STDEVPA = STDAVVIKPA
STEYX = STANDARDFEIL
T.DIST = T.FORDELING
T.DIST.2T = T.FORDELING.2T
T.DIST.RT = T.FORDELING.H
T.INV = T.INV
T.INV.2T = T.INV.2T
T.TEST = T.TEST
TREND = TREND
TRIMMEAN = TRIMMET.GJENNOMSNITT
VAR.P = VARIANS.P
VAR.S = VARIANS.S
VARA = VARIANSA
VARPA = VARIANSPA
WEIBULL.DIST = WEIBULL.DIST.N
Z.TEST = Z.TEST
##
## Tekstfunksjoner (Text Functions)
##
ASC = STIGENDE
BAHTTEXT = BAHTTEKST
CHAR = TEGNKODE
CLEAN = RENSK
CODE = KODE
CONCAT = KJED.SAMMEN
DOLLAR = VALUTA
EXACT = EKSAKT
FIND = FINN
FIXED = FASTSATT
ISTHAIDIGIT = ERTHAISIFFER
LEFT = VENSTRE
LEN = LENGDE
LOWER = SMÅ
MID = DELTEKST
NUMBERSTRING = TALLSTRENG
NUMBERVALUE = TALLVERDI
PHONETIC = FURIGANA
PROPER = STOR.FORBOKSTAV
REPLACE = ERSTATT
REPT = GJENTA
RIGHT = HØYRE
SEARCH = SØK
SUBSTITUTE = BYTT.UT
T = T
TEXT = TEKST
TEXTJOIN = TEKST.KOMBINER
THAIDIGIT = THAISIFFER
THAINUMSOUND = THAINUMLYD
THAINUMSTRING = THAINUMSTRENG
THAISTRINGLENGTH = THAISTRENGLENGDE
TRIM = TRIMME
UNICHAR = UNICODETEGN
UNICODE = UNICODE
UPPER = STORE
VALUE = VERDI
##
## Nettfunksjoner (Web Functions)
##
ENCODEURL = URL.KODE
FILTERXML = FILTRERXML
WEBSERVICE = NETTJENESTE
##
## Kompatibilitetsfunksjoner (Compatibility Functions)
##
BETADIST = BETA.FORDELING
BETAINV = INVERS.BETA.FORDELING
BINOMDIST = BINOM.FORDELING
CEILING = AVRUND.GJELDENDE.MULTIPLUM
CHIDIST = KJI.FORDELING
CHIINV = INVERS.KJI.FORDELING
CHITEST = KJI.TEST
CONCATENATE = KJEDE.SAMMEN
CONFIDENCE = KONFIDENS
COVAR = KOVARIANS
CRITBINOM = GRENSE.BINOM
EXPONDIST = EKSP.FORDELING
FDIST = FFORDELING
FINV = FFORDELING.INVERS
FLOOR = AVRUND.GJELDENDE.MULTIPLUM.NED
FORECAST = PROGNOSE
FTEST = FTEST
GAMMADIST = GAMMAFORDELING
GAMMAINV = GAMMAINV
HYPGEOMDIST = HYPGEOM.FORDELING
LOGINV = LOGINV
LOGNORMDIST = LOGNORMFORD
MODE = MODUS
NEGBINOMDIST = NEGBINOM.FORDELING
NORMDIST = NORMALFORDELING
NORMINV = NORMINV
NORMSDIST = NORMSFORDELING
NORMSINV = NORMSINV
PERCENTILE = PERSENTIL
PERCENTRANK = PROSENTDEL
POISSON = POISSON
QUARTILE = KVARTIL
RANK = RANG
STDEV = STDAV
STDEVP = STDAVP
TDIST = TFORDELING
TINV = TINV
TTEST = TTEST
VAR = VARIANS
VARP = VARIANSP
WEIBULL = WEIBULL.FORDELING
ZTEST = ZTEST
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/config 0000644 00000000463 15167673465 0020604 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Norsk Bokmål (Norwegian Bokmål)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL
DIV0
VALUE = #VERDI!
REF
NAME = #NAVN?
NUM
NA = #N/D
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions 0000644 00000023147 15167673465 0022002 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Português Brasileiro (Brazilian Portuguese)
##
############################################################
##
## Funções de cubo (Cube Functions)
##
CUBEKPIMEMBER = MEMBROKPICUBO
CUBEMEMBER = MEMBROCUBO
CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO
CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO
CUBESET = CONJUNTOCUBO
CUBESETCOUNT = CONTAGEMCONJUNTOCUBO
CUBEVALUE = VALORCUBO
##
## Funções de banco de dados (Database Functions)
##
DAVERAGE = BDMÉDIA
DCOUNT = BDCONTAR
DCOUNTA = BDCONTARA
DGET = BDEXTRAIR
DMAX = BDMÁX
DMIN = BDMÍN
DPRODUCT = BDMULTIPL
DSTDEV = BDEST
DSTDEVP = BDDESVPA
DSUM = BDSOMA
DVAR = BDVAREST
DVARP = BDVARP
##
## Funções de data e hora (Date & Time Functions)
##
DATE = DATA
DATEDIF = DATADIF
DATESTRING = DATA.SÉRIE
DATEVALUE = DATA.VALOR
DAY = DIA
DAYS = DIAS
DAYS360 = DIAS360
EDATE = DATAM
EOMONTH = FIMMÊS
HOUR = HORA
ISOWEEKNUM = NÚMSEMANAISO
MINUTE = MINUTO
MONTH = MÊS
NETWORKDAYS = DIATRABALHOTOTAL
NETWORKDAYS.INTL = DIATRABALHOTOTAL.INTL
NOW = AGORA
SECOND = SEGUNDO
TIME = TEMPO
TIMEVALUE = VALOR.TEMPO
TODAY = HOJE
WEEKDAY = DIA.DA.SEMANA
WEEKNUM = NÚMSEMANA
WORKDAY = DIATRABALHO
WORKDAY.INTL = DIATRABALHO.INTL
YEAR = ANO
YEARFRAC = FRAÇÃOANO
##
## Funções de engenharia (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BINADEC
BIN2HEX = BINAHEX
BIN2OCT = BINAOCT
BITAND = BITAND
BITLSHIFT = DESLOCESQBIT
BITOR = BITOR
BITRSHIFT = DESLOCDIRBIT
BITXOR = BITXOR
COMPLEX = COMPLEXO
CONVERT = CONVERTER
DEC2BIN = DECABIN
DEC2HEX = DECAHEX
DEC2OCT = DECAOCT
DELTA = DELTA
ERF = FUNERRO
ERF.PRECISE = FUNERRO.PRECISO
ERFC = FUNERROCOMPL
ERFC.PRECISE = FUNERROCOMPL.PRECISO
GESTEP = DEGRAU
HEX2BIN = HEXABIN
HEX2DEC = HEXADEC
HEX2OCT = HEXAOCT
IMABS = IMABS
IMAGINARY = IMAGINÁRIO
IMARGUMENT = IMARG
IMCONJUGATE = IMCONJ
IMCOS = IMCOS
IMCOSH = IMCOSH
IMCOT = IMCOT
IMCSC = IMCOSEC
IMCSCH = IMCOSECH
IMDIV = IMDIV
IMEXP = IMEXP
IMLN = IMLN
IMLOG10 = IMLOG10
IMLOG2 = IMLOG2
IMPOWER = IMPOT
IMPRODUCT = IMPROD
IMREAL = IMREAL
IMSEC = IMSEC
IMSECH = IMSECH
IMSIN = IMSENO
IMSINH = IMSENH
IMSQRT = IMRAIZ
IMSUB = IMSUBTR
IMSUM = IMSOMA
IMTAN = IMTAN
OCT2BIN = OCTABIN
OCT2DEC = OCTADEC
OCT2HEX = OCTAHEX
##
## Funções financeiras (Financial Functions)
##
ACCRINT = JUROSACUM
ACCRINTM = JUROSACUMV
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = CUPDIASINLIQ
COUPDAYS = CUPDIAS
COUPDAYSNC = CUPDIASPRÓX
COUPNCD = CUPDATAPRÓX
COUPNUM = CUPNÚM
COUPPCD = CUPDATAANT
CUMIPMT = PGTOJURACUM
CUMPRINC = PGTOCAPACUM
DB = BD
DDB = BDD
DISC = DESC
DOLLARDE = MOEDADEC
DOLLARFR = MOEDAFRA
DURATION = DURAÇÃO
EFFECT = EFETIVA
FV = VF
FVSCHEDULE = VFPLANO
INTRATE = TAXAJUROS
IPMT = IPGTO
IRR = TIR
ISPMT = ÉPGTO
MDURATION = MDURAÇÃO
MIRR = MTIR
NOMINAL = NOMINAL
NPER = NPER
NPV = VPL
ODDFPRICE = PREÇOPRIMINC
ODDFYIELD = LUCROPRIMINC
ODDLPRICE = PREÇOÚLTINC
ODDLYIELD = LUCROÚLTINC
PDURATION = DURAÇÃOP
PMT = PGTO
PPMT = PPGTO
PRICE = PREÇO
PRICEDISC = PREÇODESC
PRICEMAT = PREÇOVENC
PV = VP
RATE = TAXA
RECEIVED = RECEBER
RRI = TAXAJURO
SLN = DPD
SYD = SDA
TBILLEQ = OTN
TBILLPRICE = OTNVALOR
TBILLYIELD = OTNLUCRO
VDB = BDV
XIRR = XTIR
XNPV = XVPL
YIELD = LUCRO
YIELDDISC = LUCRODESC
YIELDMAT = LUCROVENC
##
## Funções de informação (Information Functions)
##
CELL = CÉL
ERROR.TYPE = TIPO.ERRO
INFO = INFORMAÇÃO
ISBLANK = ÉCÉL.VAZIA
ISERR = ÉERRO
ISERROR = ÉERROS
ISEVEN = ÉPAR
ISFORMULA = ÉFÓRMULA
ISLOGICAL = ÉLÓGICO
ISNA = É.NÃO.DISP
ISNONTEXT = É.NÃO.TEXTO
ISNUMBER = ÉNÚM
ISODD = ÉIMPAR
ISREF = ÉREF
ISTEXT = ÉTEXTO
N = N
NA = NÃO.DISP
SHEET = PLAN
SHEETS = PLANS
TYPE = TIPO
##
## Funções lógicas (Logical Functions)
##
AND = E
FALSE = FALSO
IF = SE
IFERROR = SEERRO
IFNA = SENÃODISP
IFS = SES
NOT = NÃO
OR = OU
SWITCH = PARÂMETRO
TRUE = VERDADEIRO
XOR = XOR
##
## Funções de pesquisa e referência (Lookup & Reference Functions)
##
ADDRESS = ENDEREÇO
AREAS = ÁREAS
CHOOSE = ESCOLHER
COLUMN = COL
COLUMNS = COLS
FORMULATEXT = FÓRMULATEXTO
GETPIVOTDATA = INFODADOSTABELADINÂMICA
HLOOKUP = PROCH
HYPERLINK = HIPERLINK
INDEX = ÍNDICE
INDIRECT = INDIRETO
LOOKUP = PROC
MATCH = CORRESP
OFFSET = DESLOC
ROW = LIN
ROWS = LINS
RTD = RTD
TRANSPOSE = TRANSPOR
VLOOKUP = PROCV
*RC = LC
##
## Funções matemáticas e trigonométricas (Math & Trig Functions)
##
ABS = ABS
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = AGREGAR
ARABIC = ARÁBICO
ASIN = ASEN
ASINH = ASENH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = BASE
CEILING.MATH = TETO.MAT
CEILING.PRECISE = TETO.PRECISO
COMBIN = COMBIN
COMBINA = COMBINA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = COSEC
CSCH = COSECH
DECIMAL = DECIMAL
DEGREES = GRAUS
ECMA.CEILING = ECMA.TETO
EVEN = PAR
EXP = EXP
FACT = FATORIAL
FACTDOUBLE = FATDUPLO
FLOOR.MATH = ARREDMULTB.MAT
FLOOR.PRECISE = ARREDMULTB.PRECISO
GCD = MDC
INT = INT
ISO.CEILING = ISO.TETO
LCM = MMC
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MATRIZ.DETERM
MINVERSE = MATRIZ.INVERSO
MMULT = MATRIZ.MULT
MOD = MOD
MROUND = MARRED
MULTINOMIAL = MULTINOMIAL
MUNIT = MUNIT
ODD = ÍMPAR
PI = PI
POWER = POTÊNCIA
PRODUCT = MULT
QUOTIENT = QUOCIENTE
RADIANS = RADIANOS
RAND = ALEATÓRIO
RANDBETWEEN = ALEATÓRIOENTRE
ROMAN = ROMANO
ROUND = ARRED
ROUNDDOWN = ARREDONDAR.PARA.BAIXO
ROUNDUP = ARREDONDAR.PARA.CIMA
SEC = SEC
SECH = SECH
SERIESSUM = SOMASEQÜÊNCIA
SIGN = SINAL
SIN = SEN
SINH = SENH
SQRT = RAIZ
SQRTPI = RAIZPI
SUBTOTAL = SUBTOTAL
SUM = SOMA
SUMIF = SOMASE
SUMIFS = SOMASES
SUMPRODUCT = SOMARPRODUTO
SUMSQ = SOMAQUAD
SUMX2MY2 = SOMAX2DY2
SUMX2PY2 = SOMAX2SY2
SUMXMY2 = SOMAXMY2
TAN = TAN
TANH = TANH
TRUNC = TRUNCAR
##
## Funções estatísticas (Statistical Functions)
##
AVEDEV = DESV.MÉDIO
AVERAGE = MÉDIA
AVERAGEA = MÉDIAA
AVERAGEIF = MÉDIASE
AVERAGEIFS = MÉDIASES
BETA.DIST = DIST.BETA
BETA.INV = INV.BETA
BINOM.DIST = DISTR.BINOM
BINOM.DIST.RANGE = INTERV.DISTR.BINOM
BINOM.INV = INV.BINOM
CHISQ.DIST = DIST.QUIQUA
CHISQ.DIST.RT = DIST.QUIQUA.CD
CHISQ.INV = INV.QUIQUA
CHISQ.INV.RT = INV.QUIQUA.CD
CHISQ.TEST = TESTE.QUIQUA
CONFIDENCE.NORM = INT.CONFIANÇA.NORM
CONFIDENCE.T = INT.CONFIANÇA.T
CORREL = CORREL
COUNT = CONT.NÚM
COUNTA = CONT.VALORES
COUNTBLANK = CONTAR.VAZIO
COUNTIF = CONT.SE
COUNTIFS = CONT.SES
COVARIANCE.P = COVARIAÇÃO.P
COVARIANCE.S = COVARIAÇÃO.S
DEVSQ = DESVQ
EXPON.DIST = DISTR.EXPON
F.DIST = DIST.F
F.DIST.RT = DIST.F.CD
F.INV = INV.F
F.INV.RT = INV.F.CD
F.TEST = TESTE.F
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = PREVISÃO.ETS
FORECAST.ETS.CONFINT = PREVISÃO.ETS.CONFINT
FORECAST.ETS.SEASONALITY = PREVISÃO.ETS.SAZONALIDADE
FORECAST.ETS.STAT = PREVISÃO.ETS.STAT
FORECAST.LINEAR = PREVISÃO.LINEAR
FREQUENCY = FREQÜÊNCIA
GAMMA = GAMA
GAMMA.DIST = DIST.GAMA
GAMMA.INV = INV.GAMA
GAMMALN = LNGAMA
GAMMALN.PRECISE = LNGAMA.PRECISO
GAUSS = GAUSS
GEOMEAN = MÉDIA.GEOMÉTRICA
GROWTH = CRESCIMENTO
HARMEAN = MÉDIA.HARMÔNICA
HYPGEOM.DIST = DIST.HIPERGEOM.N
INTERCEPT = INTERCEPÇÃO
KURT = CURT
LARGE = MAIOR
LINEST = PROJ.LIN
LOGEST = PROJ.LOG
LOGNORM.DIST = DIST.LOGNORMAL.N
LOGNORM.INV = INV.LOGNORMAL
MAX = MÁXIMO
MAXA = MÁXIMOA
MAXIFS = MÁXIMOSES
MEDIAN = MED
MIN = MÍNIMO
MINA = MÍNIMOA
MINIFS = MÍNIMOSES
MODE.MULT = MODO.MULT
MODE.SNGL = MODO.ÚNICO
NEGBINOM.DIST = DIST.BIN.NEG.N
NORM.DIST = DIST.NORM.N
NORM.INV = INV.NORM.N
NORM.S.DIST = DIST.NORMP.N
NORM.S.INV = INV.NORMP.N
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTIL.EXC
PERCENTILE.INC = PERCENTIL.INC
PERCENTRANK.EXC = ORDEM.PORCENTUAL.EXC
PERCENTRANK.INC = ORDEM.PORCENTUAL.INC
PERMUT = PERMUT
PERMUTATIONA = PERMUTAS
PHI = PHI
POISSON.DIST = DIST.POISSON
PROB = PROB
QUARTILE.EXC = QUARTIL.EXC
QUARTILE.INC = QUARTIL.INC
RANK.AVG = ORDEM.MÉD
RANK.EQ = ORDEM.EQ
RSQ = RQUAD
SKEW = DISTORÇÃO
SKEW.P = DISTORÇÃO.P
SLOPE = INCLINAÇÃO
SMALL = MENOR
STANDARDIZE = PADRONIZAR
STDEV.P = DESVPAD.P
STDEV.S = DESVPAD.A
STDEVA = DESVPADA
STDEVPA = DESVPADPA
STEYX = EPADYX
T.DIST = DIST.T
T.DIST.2T = DIST.T.BC
T.DIST.RT = DIST.T.CD
T.INV = INV.T
T.INV.2T = INV.T.BC
T.TEST = TESTE.T
TREND = TENDÊNCIA
TRIMMEAN = MÉDIA.INTERNA
VAR.P = VAR.P
VAR.S = VAR.A
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = DIST.WEIBULL
Z.TEST = TESTE.Z
##
## Funções de texto (Text Functions)
##
BAHTTEXT = BAHTTEXT
CHAR = CARACT
CLEAN = TIRAR
CODE = CÓDIGO
CONCAT = CONCAT
DOLLAR = MOEDA
EXACT = EXATO
FIND = PROCURAR
FIXED = DEF.NÚM.DEC
LEFT = ESQUERDA
LEN = NÚM.CARACT
LOWER = MINÚSCULA
MID = EXT.TEXTO
NUMBERSTRING = SEQÜÊNCIA.NÚMERO
NUMBERVALUE = VALORNUMÉRICO
PHONETIC = FONÉTICA
PROPER = PRI.MAIÚSCULA
REPLACE = MUDAR
REPT = REPT
RIGHT = DIREITA
SEARCH = LOCALIZAR
SUBSTITUTE = SUBSTITUIR
T = T
TEXT = TEXTO
TEXTJOIN = UNIRTEXTO
TRIM = ARRUMAR
UNICHAR = CARACTUNICODE
UNICODE = UNICODE
UPPER = MAIÚSCULA
VALUE = VALOR
##
## Funções da Web (Web Functions)
##
ENCODEURL = CODIFURL
FILTERXML = FILTROXML
WEBSERVICE = SERVIÇOWEB
##
## Funções de compatibilidade (Compatibility Functions)
##
BETADIST = DISTBETA
BETAINV = BETA.ACUM.INV
BINOMDIST = DISTRBINOM
CEILING = TETO
CHIDIST = DIST.QUI
CHIINV = INV.QUI
CHITEST = TESTE.QUI
CONCATENATE = CONCATENAR
CONFIDENCE = INT.CONFIANÇA
COVAR = COVAR
CRITBINOM = CRIT.BINOM
EXPONDIST = DISTEXPON
FDIST = DISTF
FINV = INVF
FLOOR = ARREDMULTB
FORECAST = PREVISÃO
FTEST = TESTEF
GAMMADIST = DISTGAMA
GAMMAINV = INVGAMA
HYPGEOMDIST = DIST.HIPERGEOM
LOGINV = INVLOG
LOGNORMDIST = DIST.LOGNORMAL
MODE = MODO
NEGBINOMDIST = DIST.BIN.NEG
NORMDIST = DISTNORM
NORMINV = INV.NORM
NORMSDIST = DISTNORMP
NORMSINV = INV.NORMP
PERCENTILE = PERCENTIL
PERCENTRANK = ORDEM.PORCENTUAL
POISSON = POISSON
QUARTILE = QUARTIL
RANK = ORDEM
STDEV = DESVPAD
STDEVP = DESVPADP
TDIST = DISTT
TINV = INVT
TTEST = TESTET
VAR = VAR
VARP = VARP
WEIBULL = WEIBULL
ZTEST = TESTEZ
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config 0000644 00000000520 15167673465 0021225 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Português Brasileiro (Brazilian Portuguese)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #NULO!
DIV0
VALUE = #VALOR!
REF
NAME = #NOME?
NUM = #NÚM!
NA = #N/D
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions 0000644 00000023777 15167673465 0021410 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Português (Portuguese)
##
############################################################
##
## Funções de cubo (Cube Functions)
##
CUBEKPIMEMBER = MEMBROKPICUBO
CUBEMEMBER = MEMBROCUBO
CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO
CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO
CUBESET = CONJUNTOCUBO
CUBESETCOUNT = CONTARCONJUNTOCUBO
CUBEVALUE = VALORCUBO
##
## Funções de base de dados (Database Functions)
##
DAVERAGE = BDMÉDIA
DCOUNT = BDCONTAR
DCOUNTA = BDCONTAR.VAL
DGET = BDOBTER
DMAX = BDMÁX
DMIN = BDMÍN
DPRODUCT = BDMULTIPL
DSTDEV = BDDESVPAD
DSTDEVP = BDDESVPADP
DSUM = BDSOMA
DVAR = BDVAR
DVARP = BDVARP
##
## Funções de data e hora (Date & Time Functions)
##
DATE = DATA
DATEDIF = DATADIF
DATESTRING = DATA.CADEIA
DATEVALUE = DATA.VALOR
DAY = DIA
DAYS = DIAS
DAYS360 = DIAS360
EDATE = DATAM
EOMONTH = FIMMÊS
HOUR = HORA
ISOWEEKNUM = NUMSEMANAISO
MINUTE = MINUTO
MONTH = MÊS
NETWORKDAYS = DIATRABALHOTOTAL
NETWORKDAYS.INTL = DIATRABALHOTOTAL.INTL
NOW = AGORA
SECOND = SEGUNDO
THAIDAYOFWEEK = DIA.DA.SEMANA.TAILANDÊS
THAIMONTHOFYEAR = MÊS.DO.ANO.TAILANDÊS
THAIYEAR = ANO.TAILANDÊS
TIME = TEMPO
TIMEVALUE = VALOR.TEMPO
TODAY = HOJE
WEEKDAY = DIA.SEMANA
WEEKNUM = NÚMSEMANA
WORKDAY = DIATRABALHO
WORKDAY.INTL = DIATRABALHO.INTL
YEAR = ANO
YEARFRAC = FRAÇÃOANO
##
## Funções de engenharia (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BINADEC
BIN2HEX = BINAHEX
BIN2OCT = BINAOCT
BITAND = BIT.E
BITLSHIFT = BITDESL.ESQ
BITOR = BIT.OU
BITRSHIFT = BITDESL.DIR
BITXOR = BIT.XOU
COMPLEX = COMPLEXO
CONVERT = CONVERTER
DEC2BIN = DECABIN
DEC2HEX = DECAHEX
DEC2OCT = DECAOCT
DELTA = DELTA
ERF = FUNCERRO
ERF.PRECISE = FUNCERRO.PRECISO
ERFC = FUNCERROCOMPL
ERFC.PRECISE = FUNCERROCOMPL.PRECISO
GESTEP = DEGRAU
HEX2BIN = HEXABIN
HEX2DEC = HEXADEC
HEX2OCT = HEXAOCT
IMABS = IMABS
IMAGINARY = IMAGINÁRIO
IMARGUMENT = IMARG
IMCONJUGATE = IMCONJ
IMCOS = IMCOS
IMCOSH = IMCOSH
IMCOT = IMCOT
IMCSC = IMCSC
IMCSCH = IMCSCH
IMDIV = IMDIV
IMEXP = IMEXP
IMLN = IMLN
IMLOG10 = IMLOG10
IMLOG2 = IMLOG2
IMPOWER = IMPOT
IMPRODUCT = IMPROD
IMREAL = IMREAL
IMSEC = IMSEC
IMSECH = IMSECH
IMSIN = IMSENO
IMSINH = IMSENOH
IMSQRT = IMRAIZ
IMSUB = IMSUBTR
IMSUM = IMSOMA
IMTAN = IMTAN
OCT2BIN = OCTABIN
OCT2DEC = OCTADEC
OCT2HEX = OCTAHEX
##
## Funções financeiras (Financial Functions)
##
ACCRINT = JUROSACUM
ACCRINTM = JUROSACUMV
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = CUPDIASINLIQ
COUPDAYS = CUPDIAS
COUPDAYSNC = CUPDIASPRÓX
COUPNCD = CUPDATAPRÓX
COUPNUM = CUPNÚM
COUPPCD = CUPDATAANT
CUMIPMT = PGTOJURACUM
CUMPRINC = PGTOCAPACUM
DB = BD
DDB = BDD
DISC = DESC
DOLLARDE = MOEDADEC
DOLLARFR = MOEDAFRA
DURATION = DURAÇÃO
EFFECT = EFETIVA
FV = VF
FVSCHEDULE = VFPLANO
INTRATE = TAXAJUROS
IPMT = IPGTO
IRR = TIR
ISPMT = É.PGTO
MDURATION = MDURAÇÃO
MIRR = MTIR
NOMINAL = NOMINAL
NPER = NPER
NPV = VAL
ODDFPRICE = PREÇOPRIMINC
ODDFYIELD = LUCROPRIMINC
ODDLPRICE = PREÇOÚLTINC
ODDLYIELD = LUCROÚLTINC
PDURATION = PDURAÇÃO
PMT = PGTO
PPMT = PPGTO
PRICE = PREÇO
PRICEDISC = PREÇODESC
PRICEMAT = PREÇOVENC
PV = VA
RATE = TAXA
RECEIVED = RECEBER
RRI = DEVOLVERTAXAJUROS
SLN = AMORT
SYD = AMORTD
TBILLEQ = OTN
TBILLPRICE = OTNVALOR
TBILLYIELD = OTNLUCRO
VDB = BDV
XIRR = XTIR
XNPV = XVAL
YIELD = LUCRO
YIELDDISC = LUCRODESC
YIELDMAT = LUCROVENC
##
## Funções de informação (Information Functions)
##
CELL = CÉL
ERROR.TYPE = TIPO.ERRO
INFO = INFORMAÇÃO
ISBLANK = É.CÉL.VAZIA
ISERR = É.ERROS
ISERROR = É.ERRO
ISEVEN = ÉPAR
ISFORMULA = É.FORMULA
ISLOGICAL = É.LÓGICO
ISNA = É.NÃO.DISP
ISNONTEXT = É.NÃO.TEXTO
ISNUMBER = É.NÚM
ISODD = ÉÍMPAR
ISREF = É.REF
ISTEXT = É.TEXTO
N = N
NA = NÃO.DISP
SHEET = FOLHA
SHEETS = FOLHAS
TYPE = TIPO
##
## Funções lógicas (Logical Functions)
##
AND = E
FALSE = FALSO
IF = SE
IFERROR = SE.ERRO
IFNA = SEND
IFS = SE.S
NOT = NÃO
OR = OU
SWITCH = PARÂMETRO
TRUE = VERDADEIRO
XOR = XOU
##
## Funções de pesquisa e referência (Lookup & Reference Functions)
##
ADDRESS = ENDEREÇO
AREAS = ÁREAS
CHOOSE = SELECIONAR
COLUMN = COL
COLUMNS = COLS
FORMULATEXT = FÓRMULA.TEXTO
GETPIVOTDATA = OBTERDADOSDIN
HLOOKUP = PROCH
HYPERLINK = HIPERLIGAÇÃO
INDEX = ÍNDICE
INDIRECT = INDIRETO
LOOKUP = PROC
MATCH = CORRESP
OFFSET = DESLOCAMENTO
ROW = LIN
ROWS = LINS
RTD = RTD
TRANSPOSE = TRANSPOR
VLOOKUP = PROCV
*RC = LC
##
## Funções matemáticas e trigonométricas (Math & Trig Functions)
##
ABS = ABS
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = AGREGAR
ARABIC = ÁRABE
ASIN = ASEN
ASINH = ASENH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = BASE
CEILING.MATH = ARRED.EXCESSO.MAT
CEILING.PRECISE = ARRED.EXCESSO.PRECISO
COMBIN = COMBIN
COMBINA = COMBIN.R
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DECIMAL
DEGREES = GRAUS
ECMA.CEILING = ARRED.EXCESSO.ECMA
EVEN = PAR
EXP = EXP
FACT = FATORIAL
FACTDOUBLE = FATDUPLO
FLOOR.MATH = ARRED.DEFEITO.MAT
FLOOR.PRECISE = ARRED.DEFEITO.PRECISO
GCD = MDC
INT = INT
ISO.CEILING = ARRED.EXCESSO.ISO
LCM = MMC
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MATRIZ.DETERM
MINVERSE = MATRIZ.INVERSA
MMULT = MATRIZ.MULT
MOD = RESTO
MROUND = MARRED
MULTINOMIAL = POLINOMIAL
MUNIT = UNIDM
ODD = ÍMPAR
PI = PI
POWER = POTÊNCIA
PRODUCT = PRODUTO
QUOTIENT = QUOCIENTE
RADIANS = RADIANOS
RAND = ALEATÓRIO
RANDBETWEEN = ALEATÓRIOENTRE
ROMAN = ROMANO
ROUND = ARRED
ROUNDBAHTDOWN = ARREDOND.BAHT.BAIXO
ROUNDBAHTUP = ARREDOND.BAHT.CIMA
ROUNDDOWN = ARRED.PARA.BAIXO
ROUNDUP = ARRED.PARA.CIMA
SEC = SEC
SECH = SECH
SERIESSUM = SOMASÉRIE
SIGN = SINAL
SIN = SEN
SINH = SENH
SQRT = RAIZQ
SQRTPI = RAIZPI
SUBTOTAL = SUBTOTAL
SUM = SOMA
SUMIF = SOMA.SE
SUMIFS = SOMA.SE.S
SUMPRODUCT = SOMARPRODUTO
SUMSQ = SOMARQUAD
SUMX2MY2 = SOMAX2DY2
SUMX2PY2 = SOMAX2SY2
SUMXMY2 = SOMAXMY2
TAN = TAN
TANH = TANH
TRUNC = TRUNCAR
##
## Funções estatísticas (Statistical Functions)
##
AVEDEV = DESV.MÉDIO
AVERAGE = MÉDIA
AVERAGEA = MÉDIAA
AVERAGEIF = MÉDIA.SE
AVERAGEIFS = MÉDIA.SE.S
BETA.DIST = DIST.BETA
BETA.INV = INV.BETA
BINOM.DIST = DISTR.BINOM
BINOM.DIST.RANGE = DIST.BINOM.INTERVALO
BINOM.INV = INV.BINOM
CHISQ.DIST = DIST.CHIQ
CHISQ.DIST.RT = DIST.CHIQ.DIR
CHISQ.INV = INV.CHIQ
CHISQ.INV.RT = INV.CHIQ.DIR
CHISQ.TEST = TESTE.CHIQ
CONFIDENCE.NORM = INT.CONFIANÇA.NORM
CONFIDENCE.T = INT.CONFIANÇA.T
CORREL = CORREL
COUNT = CONTAR
COUNTA = CONTAR.VAL
COUNTBLANK = CONTAR.VAZIO
COUNTIF = CONTAR.SE
COUNTIFS = CONTAR.SE.S
COVARIANCE.P = COVARIÂNCIA.P
COVARIANCE.S = COVARIÂNCIA.S
DEVSQ = DESVQ
EXPON.DIST = DIST.EXPON
F.DIST = DIST.F
F.DIST.RT = DIST.F.DIR
F.INV = INV.F
F.INV.RT = INV.F.DIR
F.TEST = TESTE.F
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = PREVISÃO.ETS
FORECAST.ETS.CONFINT = PREVISÃO.ETS.CONFINT
FORECAST.ETS.SEASONALITY = PREVISÃO.ETS.SAZONALIDADE
FORECAST.ETS.STAT = PREVISÃO.ETS.ESTATÍSTICA
FORECAST.LINEAR = PREVISÃO.LINEAR
FREQUENCY = FREQUÊNCIA
GAMMA = GAMA
GAMMA.DIST = DIST.GAMA
GAMMA.INV = INV.GAMA
GAMMALN = LNGAMA
GAMMALN.PRECISE = LNGAMA.PRECISO
GAUSS = GAUSS
GEOMEAN = MÉDIA.GEOMÉTRICA
GROWTH = CRESCIMENTO
HARMEAN = MÉDIA.HARMÓNICA
HYPGEOM.DIST = DIST.HIPGEOM
INTERCEPT = INTERCETAR
KURT = CURT
LARGE = MAIOR
LINEST = PROJ.LIN
LOGEST = PROJ.LOG
LOGNORM.DIST = DIST.NORMLOG
LOGNORM.INV = INV.NORMALLOG
MAX = MÁXIMO
MAXA = MÁXIMOA
MAXIFS = MÁXIMO.SE.S
MEDIAN = MED
MIN = MÍNIMO
MINA = MÍNIMOA
MINIFS = MÍNIMO.SE.S
MODE.MULT = MODO.MÚLT
MODE.SNGL = MODO.SIMPLES
NEGBINOM.DIST = DIST.BINOM.NEG
NORM.DIST = DIST.NORMAL
NORM.INV = INV.NORMAL
NORM.S.DIST = DIST.S.NORM
NORM.S.INV = INV.S.NORM
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTIL.EXC
PERCENTILE.INC = PERCENTIL.INC
PERCENTRANK.EXC = ORDEM.PERCENTUAL.EXC
PERCENTRANK.INC = ORDEM.PERCENTUAL.INC
PERMUT = PERMUTAR
PERMUTATIONA = PERMUTAR.R
PHI = PHI
POISSON.DIST = DIST.POISSON
PROB = PROB
QUARTILE.EXC = QUARTIL.EXC
QUARTILE.INC = QUARTIL.INC
RANK.AVG = ORDEM.MÉD
RANK.EQ = ORDEM.EQ
RSQ = RQUAD
SKEW = DISTORÇÃO
SKEW.P = DISTORÇÃO.P
SLOPE = DECLIVE
SMALL = MENOR
STANDARDIZE = NORMALIZAR
STDEV.P = DESVPAD.P
STDEV.S = DESVPAD.S
STDEVA = DESVPADA
STDEVPA = DESVPADPA
STEYX = EPADYX
T.DIST = DIST.T
T.DIST.2T = DIST.T.2C
T.DIST.RT = DIST.T.DIR
T.INV = INV.T
T.INV.2T = INV.T.2C
T.TEST = TESTE.T
TREND = TENDÊNCIA
TRIMMEAN = MÉDIA.INTERNA
VAR.P = VAR.P
VAR.S = VAR.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = DIST.WEIBULL
Z.TEST = TESTE.Z
##
## Funções de texto (Text Functions)
##
BAHTTEXT = TEXTO.BAHT
CHAR = CARÁT
CLEAN = LIMPARB
CODE = CÓDIGO
CONCAT = CONCAT
DOLLAR = MOEDA
EXACT = EXATO
FIND = LOCALIZAR
FIXED = FIXA
ISTHAIDIGIT = É.DÍGITO.TAILANDÊS
LEFT = ESQUERDA
LEN = NÚM.CARAT
LOWER = MINÚSCULAS
MID = SEG.TEXTO
NUMBERSTRING = NÚMERO.CADEIA
NUMBERVALUE = VALOR.NÚMERO
PHONETIC = FONÉTICA
PROPER = INICIAL.MAIÚSCULA
REPLACE = SUBSTITUIR
REPT = REPETIR
RIGHT = DIREITA
SEARCH = PROCURAR
SUBSTITUTE = SUBST
T = T
TEXT = TEXTO
TEXTJOIN = UNIRTEXTO
THAIDIGIT = DÍGITO.TAILANDÊS
THAINUMSOUND = SOM.NÚM.TAILANDÊS
THAINUMSTRING = CADEIA.NÚM.TAILANDÊS
THAISTRINGLENGTH = COMP.CADEIA.TAILANDÊS
TRIM = COMPACTAR
UNICHAR = UNICARÁT
UNICODE = UNICODE
UPPER = MAIÚSCULAS
VALUE = VALOR
##
## Funções da Web (Web Functions)
##
ENCODEURL = CODIFICAÇÃOURL
FILTERXML = FILTRARXML
WEBSERVICE = SERVIÇOWEB
##
## Funções de compatibilidade (Compatibility Functions)
##
BETADIST = DISTBETA
BETAINV = BETA.ACUM.INV
BINOMDIST = DISTRBINOM
CEILING = ARRED.EXCESSO
CHIDIST = DIST.CHI
CHIINV = INV.CHI
CHITEST = TESTE.CHI
CONCATENATE = CONCATENAR
CONFIDENCE = INT.CONFIANÇA
COVAR = COVAR
CRITBINOM = CRIT.BINOM
EXPONDIST = DISTEXPON
FDIST = DISTF
FINV = INVF
FLOOR = ARRED.DEFEITO
FORECAST = PREVISÃO
FTEST = TESTEF
GAMMADIST = DISTGAMA
GAMMAINV = INVGAMA
HYPGEOMDIST = DIST.HIPERGEOM
LOGINV = INVLOG
LOGNORMDIST = DIST.NORMALLOG
MODE = MODA
NEGBINOMDIST = DIST.BIN.NEG
NORMDIST = DIST.NORM
NORMINV = INV.NORM
NORMSDIST = DIST.NORMP
NORMSINV = INV.NORMP
PERCENTILE = PERCENTIL
PERCENTRANK = ORDEM.PERCENTUAL
POISSON = POISSON
QUARTILE = QUARTIL
RANK = ORDEM
STDEV = DESVPAD
STDEVP = DESVPADP
TDIST = DISTT
TINV = INVT
TTEST = TESTET
VAR = VAR
VARP = VARP
WEIBULL = WEIBULL
ZTEST = TESTEZ
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config 0000644 00000000473 15167673465 0020631 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Português (Portuguese)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #NULO!
DIV0
VALUE = #VALOR!
REF
NAME = #NOME?
NUM = #NÚM!
NA = #N/D
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions 0000644 00000024714 15167673465 0021364 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Español (Spanish)
##
############################################################
##
## Funciones de cubo (Cube Functions)
##
CUBEKPIMEMBER = MIEMBROKPICUBO
CUBEMEMBER = MIEMBROCUBO
CUBEMEMBERPROPERTY = PROPIEDADMIEMBROCUBO
CUBERANKEDMEMBER = MIEMBRORANGOCUBO
CUBESET = CONJUNTOCUBO
CUBESETCOUNT = RECUENTOCONJUNTOCUBO
CUBEVALUE = VALORCUBO
##
## Funciones de base de datos (Database Functions)
##
DAVERAGE = BDPROMEDIO
DCOUNT = BDCONTAR
DCOUNTA = BDCONTARA
DGET = BDEXTRAER
DMAX = BDMAX
DMIN = BDMIN
DPRODUCT = BDPRODUCTO
DSTDEV = BDDESVEST
DSTDEVP = BDDESVESTP
DSUM = BDSUMA
DVAR = BDVAR
DVARP = BDVARP
##
## Funciones de fecha y hora (Date & Time Functions)
##
DATE = FECHA
DATEDIF = SIFECHA
DATESTRING = CADENA.FECHA
DATEVALUE = FECHANUMERO
DAY = DIA
DAYS = DIAS
DAYS360 = DIAS360
EDATE = FECHA.MES
EOMONTH = FIN.MES
HOUR = HORA
ISOWEEKNUM = ISO.NUM.DE.SEMANA
MINUTE = MINUTO
MONTH = MES
NETWORKDAYS = DIAS.LAB
NETWORKDAYS.INTL = DIAS.LAB.INTL
NOW = AHORA
SECOND = SEGUNDO
THAIDAYOFWEEK = DIASEMTAI
THAIMONTHOFYEAR = MESAÑOTAI
THAIYEAR = AÑOTAI
TIME = NSHORA
TIMEVALUE = HORANUMERO
TODAY = HOY
WEEKDAY = DIASEM
WEEKNUM = NUM.DE.SEMANA
WORKDAY = DIA.LAB
WORKDAY.INTL = DIA.LAB.INTL
YEAR = AÑO
YEARFRAC = FRAC.AÑO
##
## Funciones de ingeniería (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BIN.A.DEC
BIN2HEX = BIN.A.HEX
BIN2OCT = BIN.A.OCT
BITAND = BIT.Y
BITLSHIFT = BIT.DESPLIZQDA
BITOR = BIT.O
BITRSHIFT = BIT.DESPLDCHA
BITXOR = BIT.XO
COMPLEX = COMPLEJO
CONVERT = CONVERTIR
DEC2BIN = DEC.A.BIN
DEC2HEX = DEC.A.HEX
DEC2OCT = DEC.A.OCT
DELTA = DELTA
ERF = FUN.ERROR
ERF.PRECISE = FUN.ERROR.EXACTO
ERFC = FUN.ERROR.COMPL
ERFC.PRECISE = FUN.ERROR.COMPL.EXACTO
GESTEP = MAYOR.O.IGUAL
HEX2BIN = HEX.A.BIN
HEX2DEC = HEX.A.DEC
HEX2OCT = HEX.A.OCT
IMABS = IM.ABS
IMAGINARY = IMAGINARIO
IMARGUMENT = IM.ANGULO
IMCONJUGATE = IM.CONJUGADA
IMCOS = IM.COS
IMCOSH = IM.COSH
IMCOT = IM.COT
IMCSC = IM.CSC
IMCSCH = IM.CSCH
IMDIV = IM.DIV
IMEXP = IM.EXP
IMLN = IM.LN
IMLOG10 = IM.LOG10
IMLOG2 = IM.LOG2
IMPOWER = IM.POT
IMPRODUCT = IM.PRODUCT
IMREAL = IM.REAL
IMSEC = IM.SEC
IMSECH = IM.SECH
IMSIN = IM.SENO
IMSINH = IM.SENOH
IMSQRT = IM.RAIZ2
IMSUB = IM.SUSTR
IMSUM = IM.SUM
IMTAN = IM.TAN
OCT2BIN = OCT.A.BIN
OCT2DEC = OCT.A.DEC
OCT2HEX = OCT.A.HEX
##
## Funciones financieras (Financial Functions)
##
ACCRINT = INT.ACUM
ACCRINTM = INT.ACUM.V
AMORDEGRC = AMORTIZ.PROGRE
AMORLINC = AMORTIZ.LIN
COUPDAYBS = CUPON.DIAS.L1
COUPDAYS = CUPON.DIAS
COUPDAYSNC = CUPON.DIAS.L2
COUPNCD = CUPON.FECHA.L2
COUPNUM = CUPON.NUM
COUPPCD = CUPON.FECHA.L1
CUMIPMT = PAGO.INT.ENTRE
CUMPRINC = PAGO.PRINC.ENTRE
DB = DB
DDB = DDB
DISC = TASA.DESC
DOLLARDE = MONEDA.DEC
DOLLARFR = MONEDA.FRAC
DURATION = DURACION
EFFECT = INT.EFECTIVO
FV = VF
FVSCHEDULE = VF.PLAN
INTRATE = TASA.INT
IPMT = PAGOINT
IRR = TIR
ISPMT = INT.PAGO.DIR
MDURATION = DURACION.MODIF
MIRR = TIRM
NOMINAL = TASA.NOMINAL
NPER = NPER
NPV = VNA
ODDFPRICE = PRECIO.PER.IRREGULAR.1
ODDFYIELD = RENDTO.PER.IRREGULAR.1
ODDLPRICE = PRECIO.PER.IRREGULAR.2
ODDLYIELD = RENDTO.PER.IRREGULAR.2
PDURATION = P.DURACION
PMT = PAGO
PPMT = PAGOPRIN
PRICE = PRECIO
PRICEDISC = PRECIO.DESCUENTO
PRICEMAT = PRECIO.VENCIMIENTO
PV = VA
RATE = TASA
RECEIVED = CANTIDAD.RECIBIDA
RRI = RRI
SLN = SLN
SYD = SYD
TBILLEQ = LETRA.DE.TEST.EQV.A.BONO
TBILLPRICE = LETRA.DE.TES.PRECIO
TBILLYIELD = LETRA.DE.TES.RENDTO
VDB = DVS
XIRR = TIR.NO.PER
XNPV = VNA.NO.PER
YIELD = RENDTO
YIELDDISC = RENDTO.DESC
YIELDMAT = RENDTO.VENCTO
##
## Funciones de información (Information Functions)
##
CELL = CELDA
ERROR.TYPE = TIPO.DE.ERROR
INFO = INFO
ISBLANK = ESBLANCO
ISERR = ESERR
ISERROR = ESERROR
ISEVEN = ES.PAR
ISFORMULA = ESFORMULA
ISLOGICAL = ESLOGICO
ISNA = ESNOD
ISNONTEXT = ESNOTEXTO
ISNUMBER = ESNUMERO
ISODD = ES.IMPAR
ISREF = ESREF
ISTEXT = ESTEXTO
N = N
NA = NOD
SHEET = HOJA
SHEETS = HOJAS
TYPE = TIPO
##
## Funciones lógicas (Logical Functions)
##
AND = Y
FALSE = FALSO
IF = SI
IFERROR = SI.ERROR
IFNA = SI.ND
IFS = SI.CONJUNTO
NOT = NO
OR = O
SWITCH = CAMBIAR
TRUE = VERDADERO
XOR = XO
##
## Funciones de búsqueda y referencia (Lookup & Reference Functions)
##
ADDRESS = DIRECCION
AREAS = AREAS
CHOOSE = ELEGIR
COLUMN = COLUMNA
COLUMNS = COLUMNAS
FORMULATEXT = FORMULATEXTO
GETPIVOTDATA = IMPORTARDATOSDINAMICOS
HLOOKUP = BUSCARH
HYPERLINK = HIPERVINCULO
INDEX = INDICE
INDIRECT = INDIRECTO
LOOKUP = BUSCAR
MATCH = COINCIDIR
OFFSET = DESREF
ROW = FILA
ROWS = FILAS
RTD = RDTR
TRANSPOSE = TRANSPONER
VLOOKUP = BUSCARV
*RC = FC
##
## Funciones matemáticas y trigonométricas (Math & Trig Functions)
##
ABS = ABS
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = AGREGAR
ARABIC = NUMERO.ARABE
ASIN = ASENO
ASINH = ASENOH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = BASE
CEILING.MATH = MULTIPLO.SUPERIOR.MAT
CEILING.PRECISE = MULTIPLO.SUPERIOR.EXACTO
COMBIN = COMBINAT
COMBINA = COMBINA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = CONV.DECIMAL
DEGREES = GRADOS
ECMA.CEILING = MULTIPLO.SUPERIOR.ECMA
EVEN = REDONDEA.PAR
EXP = EXP
FACT = FACT
FACTDOUBLE = FACT.DOBLE
FLOOR.MATH = MULTIPLO.INFERIOR.MAT
FLOOR.PRECISE = MULTIPLO.INFERIOR.EXACTO
GCD = M.C.D
INT = ENTERO
ISO.CEILING = MULTIPLO.SUPERIOR.ISO
LCM = M.C.M
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MDETERM
MINVERSE = MINVERSA
MMULT = MMULT
MOD = RESIDUO
MROUND = REDOND.MULT
MULTINOMIAL = MULTINOMIAL
MUNIT = M.UNIDAD
ODD = REDONDEA.IMPAR
PI = PI
POWER = POTENCIA
PRODUCT = PRODUCTO
QUOTIENT = COCIENTE
RADIANS = RADIANES
RAND = ALEATORIO
RANDBETWEEN = ALEATORIO.ENTRE
ROMAN = NUMERO.ROMANO
ROUND = REDONDEAR
ROUNDBAHTDOWN = REDONDEAR.BAHT.MAS
ROUNDBAHTUP = REDONDEAR.BAHT.MENOS
ROUNDDOWN = REDONDEAR.MENOS
ROUNDUP = REDONDEAR.MAS
SEC = SEC
SECH = SECH
SERIESSUM = SUMA.SERIES
SIGN = SIGNO
SIN = SENO
SINH = SENOH
SQRT = RAIZ
SQRTPI = RAIZ2PI
SUBTOTAL = SUBTOTALES
SUM = SUMA
SUMIF = SUMAR.SI
SUMIFS = SUMAR.SI.CONJUNTO
SUMPRODUCT = SUMAPRODUCTO
SUMSQ = SUMA.CUADRADOS
SUMX2MY2 = SUMAX2MENOSY2
SUMX2PY2 = SUMAX2MASY2
SUMXMY2 = SUMAXMENOSY2
TAN = TAN
TANH = TANH
TRUNC = TRUNCAR
##
## Funciones estadísticas (Statistical Functions)
##
AVEDEV = DESVPROM
AVERAGE = PROMEDIO
AVERAGEA = PROMEDIOA
AVERAGEIF = PROMEDIO.SI
AVERAGEIFS = PROMEDIO.SI.CONJUNTO
BETA.DIST = DISTR.BETA.N
BETA.INV = INV.BETA.N
BINOM.DIST = DISTR.BINOM.N
BINOM.DIST.RANGE = DISTR.BINOM.SERIE
BINOM.INV = INV.BINOM
CHISQ.DIST = DISTR.CHICUAD
CHISQ.DIST.RT = DISTR.CHICUAD.CD
CHISQ.INV = INV.CHICUAD
CHISQ.INV.RT = INV.CHICUAD.CD
CHISQ.TEST = PRUEBA.CHICUAD
CONFIDENCE.NORM = INTERVALO.CONFIANZA.NORM
CONFIDENCE.T = INTERVALO.CONFIANZA.T
CORREL = COEF.DE.CORREL
COUNT = CONTAR
COUNTA = CONTARA
COUNTBLANK = CONTAR.BLANCO
COUNTIF = CONTAR.SI
COUNTIFS = CONTAR.SI.CONJUNTO
COVARIANCE.P = COVARIANCE.P
COVARIANCE.S = COVARIANZA.M
DEVSQ = DESVIA2
EXPON.DIST = DISTR.EXP.N
F.DIST = DISTR.F.N
F.DIST.RT = DISTR.F.CD
F.INV = INV.F
F.INV.RT = INV.F.CD
F.TEST = PRUEBA.F.N
FISHER = FISHER
FISHERINV = PRUEBA.FISHER.INV
FORECAST.ETS = PRONOSTICO.ETS
FORECAST.ETS.CONFINT = PRONOSTICO.ETS.CONFINT
FORECAST.ETS.SEASONALITY = PRONOSTICO.ETS.ESTACIONALIDAD
FORECAST.ETS.STAT = PRONOSTICO.ETS.STAT
FORECAST.LINEAR = PRONOSTICO.LINEAL
FREQUENCY = FRECUENCIA
GAMMA = GAMMA
GAMMA.DIST = DISTR.GAMMA.N
GAMMA.INV = INV.GAMMA
GAMMALN = GAMMA.LN
GAMMALN.PRECISE = GAMMA.LN.EXACTO
GAUSS = GAUSS
GEOMEAN = MEDIA.GEOM
GROWTH = CRECIMIENTO
HARMEAN = MEDIA.ARMO
HYPGEOM.DIST = DISTR.HIPERGEOM.N
INTERCEPT = INTERSECCION.EJE
KURT = CURTOSIS
LARGE = K.ESIMO.MAYOR
LINEST = ESTIMACION.LINEAL
LOGEST = ESTIMACION.LOGARITMICA
LOGNORM.DIST = DISTR.LOGNORM
LOGNORM.INV = INV.LOGNORM
MAX = MAX
MAXA = MAXA
MAXIFS = MAX.SI.CONJUNTO
MEDIAN = MEDIANA
MIN = MIN
MINA = MINA
MINIFS = MIN.SI.CONJUNTO
MODE.MULT = MODA.VARIOS
MODE.SNGL = MODA.UNO
NEGBINOM.DIST = NEGBINOM.DIST
NORM.DIST = DISTR.NORM.N
NORM.INV = INV.NORM
NORM.S.DIST = DISTR.NORM.ESTAND.N
NORM.S.INV = INV.NORM.ESTAND
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTIL.EXC
PERCENTILE.INC = PERCENTIL.INC
PERCENTRANK.EXC = RANGO.PERCENTIL.EXC
PERCENTRANK.INC = RANGO.PERCENTIL.INC
PERMUT = PERMUTACIONES
PERMUTATIONA = PERMUTACIONES.A
PHI = FI
POISSON.DIST = POISSON.DIST
PROB = PROBABILIDAD
QUARTILE.EXC = CUARTIL.EXC
QUARTILE.INC = CUARTIL.INC
RANK.AVG = JERARQUIA.MEDIA
RANK.EQ = JERARQUIA.EQV
RSQ = COEFICIENTE.R2
SKEW = COEFICIENTE.ASIMETRIA
SKEW.P = COEFICIENTE.ASIMETRIA.P
SLOPE = PENDIENTE
SMALL = K.ESIMO.MENOR
STANDARDIZE = NORMALIZACION
STDEV.P = DESVEST.P
STDEV.S = DESVEST.M
STDEVA = DESVESTA
STDEVPA = DESVESTPA
STEYX = ERROR.TIPICO.XY
T.DIST = DISTR.T.N
T.DIST.2T = DISTR.T.2C
T.DIST.RT = DISTR.T.CD
T.INV = INV.T
T.INV.2T = INV.T.2C
T.TEST = PRUEBA.T.N
TREND = TENDENCIA
TRIMMEAN = MEDIA.ACOTADA
VAR.P = VAR.P
VAR.S = VAR.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = DISTR.WEIBULL
Z.TEST = PRUEBA.Z.N
##
## Funciones de texto (Text Functions)
##
BAHTTEXT = TEXTOBAHT
CHAR = CARACTER
CLEAN = LIMPIAR
CODE = CODIGO
CONCAT = CONCAT
DOLLAR = MONEDA
EXACT = IGUAL
FIND = ENCONTRAR
FIXED = DECIMAL
ISTHAIDIGIT = ESDIGITOTAI
LEFT = IZQUIERDA
LEN = LARGO
LOWER = MINUSC
MID = EXTRAE
NUMBERSTRING = CADENA.NUMERO
NUMBERVALUE = VALOR.NUMERO
PHONETIC = FONETICO
PROPER = NOMPROPIO
REPLACE = REEMPLAZAR
REPT = REPETIR
RIGHT = DERECHA
SEARCH = HALLAR
SUBSTITUTE = SUSTITUIR
T = T
TEXT = TEXTO
TEXTJOIN = UNIRCADENAS
THAIDIGIT = DIGITOTAI
THAINUMSOUND = SONNUMTAI
THAINUMSTRING = CADENANUMTAI
THAISTRINGLENGTH = LONGCADENATAI
TRIM = ESPACIOS
UNICHAR = UNICAR
UNICODE = UNICODE
UPPER = MAYUSC
VALUE = VALOR
##
## Funciones web (Web Functions)
##
ENCODEURL = URLCODIF
FILTERXML = XMLFILTRO
WEBSERVICE = SERVICIOWEB
##
## Funciones de compatibilidad (Compatibility Functions)
##
BETADIST = DISTR.BETA
BETAINV = DISTR.BETA.INV
BINOMDIST = DISTR.BINOM
CEILING = MULTIPLO.SUPERIOR
CHIDIST = DISTR.CHI
CHIINV = PRUEBA.CHI.INV
CHITEST = PRUEBA.CHI
CONCATENATE = CONCATENAR
CONFIDENCE = INTERVALO.CONFIANZA
COVAR = COVAR
CRITBINOM = BINOM.CRIT
EXPONDIST = DISTR.EXP
FDIST = DISTR.F
FINV = DISTR.F.INV
FLOOR = MULTIPLO.INFERIOR
FORECAST = PRONOSTICO
FTEST = PRUEBA.F
GAMMADIST = DISTR.GAMMA
GAMMAINV = DISTR.GAMMA.INV
HYPGEOMDIST = DISTR.HIPERGEOM
LOGINV = DISTR.LOG.INV
LOGNORMDIST = DISTR.LOG.NORM
MODE = MODA
NEGBINOMDIST = NEGBINOMDIST
NORMDIST = DISTR.NORM
NORMINV = DISTR.NORM.INV
NORMSDIST = DISTR.NORM.ESTAND
NORMSINV = DISTR.NORM.ESTAND.INV
PERCENTILE = PERCENTIL
PERCENTRANK = RANGO.PERCENTIL
POISSON = POISSON
QUARTILE = CUARTIL
RANK = JERARQUIA
STDEV = DESVEST
STDEVP = DESVESTP
TDIST = DISTR.T
TINV = DISTR.T.INV
TTEST = PRUEBA.T
VAR = VAR
VARP = VARP
WEIBULL = DIST.WEIBULL
ZTEST = PRUEBA.Z
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config 0000644 00000000516 15167673465 0020613 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Español (Spanish)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #¡NULO!
DIV0 = #¡DIV/0!
VALUE = #¡VALOR!
REF = #¡REF!
NAME = #¿NOMBRE?
NUM = #¡NUM!
NA
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx 0000644 00000422217 15167673465 0022423 0 ustar 00 PK ! ����� � [Content_Types].xml �(� Ėˎ�@E������gQٞ�L�LF�����i�~�����`,'�
�Hـ��{n�����ޖ�"�f�!�����q�����=�"$�rUz3q O��� �p�Ù(��W)Q`f>��/K�"~�+�^�������8J����,զ��۞_7NƉ�W�fB�P���ʭ���~�4r�7��3T� �2�01�'�B^dF(q�UƑ�1,L�O���ܓǿD��� *�u���O^�hrH^U�����})w>�ޯ��"C�^��*�ډ�����o#���x�O>��z�T�2�#J�����"*B�F|LW�8��𡽭����Z�<Uт��h���}lYLa����؞�v�ػ}������?�P6���j"�� ,�ml(I�:(��1�ik��L�0�{��s]`�Br�}�> ��ëH�K��4�D2pꦗ�Ɖ�w��z���
e�
��w��pY�i� �� PK ! ��, t _rels/.rels �(� ���N�0�;�徦Bh�.i7��x��Um�(�`{{��`�FAbǶ�:?��d���x�r�,�\
t�L��R�,GwRDg�#���b������;��S\5>�T��R����RQ��B�ȣK_*
8=�Zy�-Ԩ�y~��q
9��sS�07WR,�>�����"��)�ȇ$ܤ^�B�\JC�)���D��R�� �F�Tq��*��F��,�~��
��at��3�փ;vl�^"�������>d���Xħ���юF�]�C��sZ�:2�_6�����I�oϠF����ݕ�; �� PK ! n�Ɲc o xl/workbook.xml�Vmo�8�~���LP�* AW�]Um���r����P����B�lN�l7Jl�?��g�\~ira�2UqYD_��b�)/��w��dU�)�`zc�r��_�;�^�R�X PTʴ.Cǩh�rR]Ȓ �H�
K�u�R1�Vc:�纾�^�!T�`�͆SKZ���b�h0��xY�h9=.'�.m*� �\p�ւ"+���������(����.^�N��9U��}�Ng��u0>
As�b����`��?i�������h��r%��}mt��CW�.�cG]���W��L d R�E�5K#4��ܱ�
U����z��ȹ:�NY)ېZ�%�����M �Th�
��\x���O9�b�3 ���5W
���HhH��љU+�y��r�*����U���.+ޫl5'��]q���D��֒��
�j��@4:���#�����V<_�7���
�N��b��t�sAU����b���l�z�p<�A��M����x��Q~H%�u�'�����~"�%M/�nX��o�c�����}7����ٮz��YZ�/R����
�ߎ��V��S������n�ƷX��vS���if
&�6\A�L�
d��+[�u��22^D����>��m�#�淍�hg�h�g�P&�C^�M����$g�,���u�M,NO&u&B+�pl���r��n�Pofj�`��k�M������h2s0!��=āk�f��� ����(1t01��O���ӞfD�P~���uh�d�{��t��UOxW���� �Z��TN�T��]ޞ�{�X>?%�*Oog�t��otH �>�N�_�� �� PK ! �� � xl/_rels/workbook.xml.rels �(� ���N�0E�H���=q� T���-����C��G��Q
�J��Xε|����z�i:�[g%ϒ�3�ڕ��%�=]�s�Q�Ru�= ���g�T�Oش�X����?���0q,�T.���J�U
"Oӕ�=x1�d�R�-�9��:��vU�jxt�̀�GZ�H\@�*�%�Q���8C~N��� ĉ� �^�%��a�Y�cZ�*&�1��݉,��]xZ>g�0����$nϙ6*@�m>N43y ��0������C��^�n�� �� PK ! `�d� �3 xl/worksheets/sheet1.xml��[o�0��'�; �����*Km���쀓�f�iRM��;Y�M�Q�߹����,�g&��W ¶�,V�<˫}��}��"d5�V-x���t{�����Ss`LZ@�����ӤV���5�`g�EI%<���ԂѬu*��n�4�PGX�)���)���d�� �TB��!�M;g�x��'�U�s��]�3��_���7|'픗N��u���Ug�N)����X� \Cqۼ��K[.��t�i_qA����4���>�E��:��i:��;9 �}G��\IkD��u��0p��v��1�����^��~���+?��k�~��!��&���f��=U�%�.A+�X�q���e���9;5���ʖ�'�� ���iŬ��5A$�p�^�'��%y����E�}d�T�������� ۘ�`��(��^��Vk����%�J���K�KT�]!�����%�*(��]��u�&]ںa�v^<+c;z,�#?}d�TN��i)�/��;֤0���(n����U�j��=��v=�<���~�a�-k�Fu~�(�G����1�e-֞فO�0�O�@Z�29��ª� v�Ȥ�����!s�ͪ��X5۱t�I�b`O�4����:B"�F�`�!�:��9�ГNL����(��\�tG`b|
XB���T�X+B�����ύ�Z�Ю��$�ւ2�s�L�
S�o�憺$0m۳Q�ەI��QF�!�v�8�=�yA�6�1����"Z���b<���2��f�u���8?L/?��Uƈ1��D�O�7��S�@����_� �� �����n�8�_%�lDK���
���Q>�i��i����ݷ_�.b��Ē�ܵ��s�h���������C�埻ׯ5U�;����h������~z���������ÿ*x|�����������S.��9�p4��|h���χ��/N�r� lR[$�6�#��]�%��z���v}��o����%+�6g$�M{4vI*�-i"J�٦����DI0�\�{p�B��E0�Rro٧[�$ѽk��N�t��.sI�_� ��$(�I0�^pp=�N�tJc�$4�4�� �,)�ux�ޔ���R�����B�bj��I�CG$��]�dj$�D���L̀�Q�o(� i$�N{�i,JBA�Hvb LI�Cm��:�tsAR�P���R�x�j�$S5�k�d��.�t0A撌 �n+J�N;�D;�E�H��)�DHA��a�$�X��_I���ޛ�PG[Uð~�Ʌ�U���RK]R�ĶE���@�t�|F^OT�$SZ�WQ�$��H:�fiqI�!ai���N��s&I���\�{�`�0diHq c�\�)-8�Z >�턂��$SZ[QL���C�
�p:���y�--A+��]��*-�'��$�HF-˅p�B�<}~u%R���U!���UH�*�qҴ
i^���BZV!����UHYҾ
�P��$�X-�ŰK�YV����
���\�K$9���İl�$9g������oK i��35���˧
�Y}�f� }x]��:<�s
W�0�`\j�����
������`8t��9`x,I��e�N:� ��A_q�w=��cA+�5{�
$��~jH�`�����FL�#&6�j6�N�G�f�٨�Sw��I{���0�: p~�g�Ew�|*戀:�Q@�Vm �(p�f�BP5���\H�g;��.q8�G������!q�Tr�1q8x<����a�qg2l۟3���~K��*9���P>����%��zg�k�'�`�8�3#^@%�M��p>+�~+��h��ߒXZA�4g]r4��J����X�� p'�#��10!O�st7'�[��2����������a�W���Q�N���e���g�7��yD�e���V�a��
1�m∁�:�l�8b�G�y�����Ԁ��P�����l��#f�a�y���,��?���|܇3���8��#�����鐐�$F�������X�=O�Ю�\�ﱉOsV!G}>w�j �#}����8|<$��S�Aabq��S
<=![N���K�|F���+�^Nwu�#;�w�+{�FV��O�� ��� ��;+8���ͭΣfd<�X>�4'�B�p��#�:%x�8G��G�
�;@8�s��d��g��!�z^b�E ��|Dتd���(�iq~�9�4�����ჩ�2ï[.����?j,�U�K������S��� ��ƹ,�_�� �� ��D��
�0�_%�XA������'X�6Y�ٰ])���P��73̴#]Q#�ɍ4����N9�U���{w3y��i��A�Vh������ʼn2eCc���)�-g�;X�#E||.�3��5kf�甈�� �� PK ! �@|�� T� xl/worksheets/sheet2.xml��ێ�0��+���1Q���J��nz�v�I�Lmg���w8�M+�J�x������CY8�L*.�aw�Ve"��&E_�<Nb�(M���b):2���[�|Q[ƴ�J�h�u=�<�mYI�+jV����%�p)7��%�y�T�?�F^Iy�Z�\a���g�^d��U��HVP
��-����A�\�=�j�9+�s�arQ_�3)�Xk7�זv�e�%�YfC-�|�� ��܊\�v�Sf�JH�*�D���9H����L/w^2�N�K%a0�${��Z=���8<����`F.9��<E?�A��,��'��Nn0|E�����>�BW����LW�d���2�ȻZ4���^�Ŏ��%+X�삑cFg%ċy�#�4fM+��5s�B?�9<vW�+-�Ol��XQ��3A�4eO�����Z��|��M�n�@�M�����Rԏ�M���T��ݭM��ݻD����-�~��dz'��c��c��x�N��tW�g���L� #q��x����u R�A��D�vJn�k04�Њ�s��h��8"��7%飙$xjŔ~4G q�S ��yS�lx���~�Q����R�{
��(q�,���p<�@`���
�?�uX-��Ь���Q��Z�?N��������tX�..��k����Ʒ�O4��:���X}��� l9�xm�u� z��Z�q[u���I;T� [��wp?�C9�¸����$ƌ$�6A��4Nb��JlMl{�������&�Gj�8^��
�� ������q�_��`�^�BV���u��uy���"�$�Z��o?kwk���=���`\�L ��_�ן���O����_��?��_~(��_���������?����������O���U6?�����ݟ~��O�ߊ������?���B����E��?V�������?�f��~����m�Ѧ�t��&���t�q¨l7n��Fͦ�O� �����c4�
nw�FU�ڝ'��my�Fe�&�NUM��&�JS��}��
n�1�v[4n�0
��3�u��~M�w��O� ���2����%�����|����淿�A~|Ӽ�
�&��O|�0*[Tx��g&��
�8F�zh��h$=��<i���2i�&�NA�I#��>i�&��0j�E� ����h̺��^Ѩk4��7�:�S�n�ng���u���:��!�nU��F#�
�M55��8aT�+�G#��;L�p�F�7�i� j����vB�.�H�5�=Y��I#8~�4B;}L�T�c�h(У<'��T^F]G}G#��0}k��Bߟֿc����O��h�=�m�����v�c4N�ۻ�2�8D�2*��'����4UR�ڝ��ZJ~�'���)#>���;SM0���TI���2b���2*p���Om2r4ɷk$�����Og������<
���Oq�!>�+t^Gs{h��S�V��k�K/)V��6���5���o�vݭR�[m��ax(Q��*\s�2�u��{��MX�*a�k<������k�ǣ�CS�S,a���b}��ejuN���K/)���6]
WF�R�7�i�)ū�an����P����)�W�����6amV K�߄��n��c1(�ǣ��}�%,��p���1�Т;<ejuNYU�rI�����j�bWyKqߠg�E�en����P�䧵GMY�w]�gۄկ��� /�6eg/y<�#B�)��p]��c�%,�w�L��)��8�N����t5\�����o8����2|�_-�0<p\����˗����mmS�g4
�f��?�����[�;�]�)nK�'�KZx
�!`�bI=�)W�s
�-'�ڎ/�5_�[�{���~1z-���xJ��i-R����a�ls���b��>Ϳ��1�Q�]�{�7���K�.{�R��f��h\���l��F�2�/�ꎵ�:��yތ���r�\�\�W��|(ѿ>�ijJ��cH�=��.�����ee��uZ���-�Ѹ�3���~p^3|v4.�ᙝ��;��p�ů�j�lׅ�ߌ���Q<�+�m�.<�����MSS�/�c��w�}���3��Q^o��h�X]H'N��we)���n��b`?i�q�aҊI��U��M�ž�<i�W�2ժ��>�C��6Yֆc�I+�(?������b��9m���kҊ!��s��3ҷ�3�y�Fe�e�5m �<���_��q��̲8�L;Wo���d\iC�v^Njq
�Yv^q�q3�s�|G��;q�Z~-·2�ӧ��SHo��:��w�)��Fr�
��H�{��<�k ���%���c���zI��<�U��M]�rv^p�v�u����9����a��G��}z��C�$��NMɾ�wl���W'��S\p��,���-ޖ:e/�3�6�F�%ߣ�qI�k%����KraŏQ�$���y����cp���>7�=w�^���a͠����=x�Ԕ��y��yϷ�KN�5�� c��ŶL�2q���y�^`t^�q�C��5CsG��c��ˡ~g�?M�2K�b/{3�s�}��(�ýk�B<��e�:5%�Z��{��]r
%���E{��e�Ջ���h�a�{��f�5vG�Rg�۟
kf��q ��30Z0�Q�{��w����p>�u ο*��o{כk�f�HNͶ��|��
_���7,~�x���f��h��~��9�/�%7��p9��7�=������EŻ�p>�ɫ���+_��|˛ڪuQ�O�j�V�Ƶ&|4^e��:8�9S>�ܘe��,����Ÿ�\��cț;Z����e�=�\(��֩�A~-�=�.�u��*
�6\µ5�1��y��h4�%� �F��kNK�^���'��q��qI���y�1��x�Y���i�{�һ��kϟ�:5�ɯ���o}���b���U2���2{kX���y�^`4.�q!��~�a2�y�i�����d\�C�g�B��%W��7�=gw�>�
�]K���9|z��.���{��]rL���T2O$�i�a}X�re���K��{��fÄ��Ӗ�q}X��3.ɡ�s���R��+#o�{~v�{��>%C�ȇWn`���S�r���o|Wܺ�CeI��I���P�.� G�Ʌ>�S�?w��k�[��%8���5�?/�w1���9�3��{�_��ε�=�\���Ʃ�U~-T�=�.�u)��b�S�Lr��hXzC���7&�ל��KoL8��пYD?�w��%7�����������Wx۵�=�Lq?�mj~�_��=�.7�~�G����R�;j�q�HK��t�J� �Z����I+~f��V\�w�����U��i����yҊs��T����V=C��V��'��-����֍I�8���j��ş�⢐��s�c]�����\�o������ڽq%F��s^s�~\��i�g��`�wY���b�f��p�m�w���P���ʿ���.N]��̮�_�%�I�����:��g�H&��ɽYW^�`29 Λ#���+�b��v�����i��+�ؒs^J�u ���9?�����a��/,�\L�<ȹ8������z]H��k�9�Fk�|t^����fÞ�༦��5�B�5������[��&��+Fto�{~�~9c��\�X�����Oo�px��y���{��]p��uP�hhk����-�����%8�%u^3�v4.�q�߿�h������1�s]���xO=����;mKx8���P���7��+�Z�zrU>�Z[Ȏ�~�JpLS.�Ѹ�0�e\��X�y��h\��*�l��N>ы�4�P���o��o��Ӗ����k���4ᰗ��. 4���b��%�4Z�9߶N��č�|4.91ve\r��R�5��G�eh'�U���y�gv�uL�\�3��m�����T��|�J�7N8��c �=�.��X�w �⅌��,>[��X��+ڈ�����8*��*f�Nv=�_g���e������o��o�� N[����"~��6<ڗ��|ӻܾ�f��luo���m���:�G�%ǜ{����kǏ�տq��q~�N>ыq�,�v;�8��ƻ�>L������+���4᜔��.�8��.���w�͎��F�:�����g��v�>�x��%7溝�^�Kn��l\����E���k�1�s]h���j���a2`�}�}=�j�?��pz�˛U�7���+�]b��"�T��c��]���k�����Kl�[�f��h\b��
�� `�*�}\�k�6���+NDn�{��ww.L�|�����O��a��y�k]N_q��u:�Zv�,�ѸX�Y�j�
�u�^Gr����_sfzZ���.���X��勇{��w����p�NQy9�ح�����|�Gn��_'�����Ȍ+���hso\����k�����ɸ>����yAA]���� �'"7�=�ֻ��q;�r�p>p1��Kg��*��o|�Wn������6v�u���n���K�7�ƛ
W{��5��s�e:��1>U�˱�qɍ�$�'"7�=�xw� t�Z>���џ^z8S����=��.���w�-���6)U��@����l�Kn�8�(9�ܘ���M�F%7��b\r�L�'"7�}X���m�\�����E�O�z84��K�~�Ӻ�c˨�8�F%'j�h\�q3����k���%'&���5�r��.�%(n�t^q�q3��s����9~�s-���kߞ�{y�ul��|���e[nj7tk��8��-�Ѹ��~��� �c0������y����/��x]���xN��qm�;��p>pY�3[�k���w���4id���C�����ȃ���a~�빊�<|�P>w��z�7r�S��x���r�ߋ�������7iXO��p��#9�ʞ�Qr<�#9Wܐ�X p�î�x*7�������ѓ<��~/��.Jޤ��.)��H>^~X���9x>�3�M��r!�[����gf��~�C�Or��k�}���W7v~H� ��v�F���A���}30��f�y4�,W�W���d�$^�k���+�o�{���^���5�<w~���7Nz�֯'D�*��o|ܺ�ٍ���F%(.�]��Q�=�>0��f��h��I��Kp!�־*8H��\� ��e�v}O=z!�cε�=�r�I<�qf/�����7���֔�'[�!8���|4�������׃�1��q ��534��.�%8.7p^1Fp3�sPp��c�ǝ�Q��QG��WԞ�8�@��B����[�ܽ��5�BN�i;�
��#Jgco\1Ff����Kp\�b\=>�g�;��q �{�W��ߌ���ݛ�#swN�
(8;
;�,��'�g�r�q��o|ܺ��r�p�[��2�g\�Crp^s�x4.�q}K�vg��q�Ÿ�"��?�7�=�xG��l��.����dOo�p���y���{��]n��iӸ+��[�
GP�>:/9H��ܘ#v^s��Ѹ��.�ڝ�Jn��b\rc��y���xO5��9Nܹ�;���ϔ=�i�qf/���w��;8�����9�:m8~�-��yN4��Ń���G���S�vg��3��%7f��W��ߌ�T�ݝ�t��k����<�x�ӛ&e�r�%#
�ӺcAZ[��3�6ao���q1�qɉ`��'��^~��~^��b\�b��yŏ��xO��
s$��εܧ�p~��i\�4�k����w��;���hp��b��7̮V���h\�u~0��so�]��>�K~���oq���;���b\rc��yb��{���΅ɀ/vg���·2{zӄC�^��=��.7���?:���,�ϡa��Mۇ0�J*xx��I+��V�8q��
�.LY58�&�b0�<i��e�UN˯�VQ��)�a��}�,N�?���?&�¹��V��I+�^z��r�>�v�WSg�.K�,�g��`J�ƫ�kw���Yz��k��Y�X��s�������۴խ�3��3sڹ��]��Y��҃Q�-�Ӹ���p��s�^��,�g�G�>j�\�l��vm�)wi\�Y�m��t��}��J[��K[��fK?g�%K�Yz��{�~d�c����ʖ��oo�ֺ�q�!9��f�.K�,�g�����Ѹ�ŕ����Yz��k��Y�X��s�������۵�.Tܥ�b�-�F5���>:/9��g�~0*�!�q4���gK?����q�|Q���*Hi��D���qY�;�r���y��7M8-���>��|ӻ����4Ṕ�֨�ƥL|t^2����`Tb�FI���gK?�ظ�Ѹ��eLΫ��{j���q��;�r���y�ձ�7M8*���.��|ӻ�֭���02�N[��1-��y�$�>{��Q�
}�Ѹ��Us���F����Ÿ��%L�+��7�=�xw��k�C��<���ӛ&��r�q�{��]l�dwi��}�6KwF%E�,��2��+c�%���O-�ޝ��lTRC�r1.�q����xO%��9��u�Z�M}d��4�\���|�]H��xw�g~�F���^-)��������qI��}��Q8�������lTb�p�&9��kތ����s�?ܹ�a�����uOo�p���y�,�v9�[<ޥ+���m�tgT�c�F�z\<�¸�ĕ��ñ�Ǭw�,=/�v1�_��#��|3~��΅��/g�呭�Ө�%�B���i�)']�guU�FS�Ւ>�__r��7.)q?��p��Ѹ�%�����lTB�"��9�x��ߝZ�;�w_�0�Ý�p�� gE��w�z|��f�)&���B��g��tgTb�gb4����m�%&��uN�<f�;e�y���qI�˅�W|Qn~w*��Csw��֎G�jO��Z�.��K�ٻ�֝N�I���Ǖ�J*��n��ƫ�{��%%��uN~;W�ĥ��ڝݻ"䵍KL\*�\���_o~w:ww��B�=����|f��Z��{��]l��&٤�����F%6�M��G�%���u���Q^G��qgkw6�^���Kl\(��Fߝ�ݳ�}�s-�d<���|zӄE_��7��m��%�46Ϲ�֨�����| ���F�pP�Ѹ�Ƭu��Q���y�c�Ε|b�f-K���Wպsm82�y��˧7M8N���B��|ӻ�֝[�Ic��͌Jl<,��y����`Tbcl��4v��Q���y�c��+n ���p�n���w��f����Oo�p���yǽA�w����o��<G�[�g�|t^��w?���7.�1��-�l�*�y\�Kl��;��9܌�t���q��;�r���y��˧7M8l���Π�|ӻ����7il��m��t��}��Jf��Ѹd��v��s�^��7�=ݺg/���G�>����va���o�h;[���qߺs���c��yɖ�o����p�ѸƄv�vg��ǘ�1�~����~3�߳�}�s-��?��{zӄSl_�7�r��oz�ۺX�&
s��5*��H(�ږI��A2%[|o�Kn��<sp4.�1����9K/F%6&��W���xO|��sO�Ѫ����caOo�p���&ِ�bZ�ߤac�-[�m�E��s�"(&���lѽ]/1qϓ�p���y���٨�.���a���yͻ����s�\�%w�����Ÿ��|~cx��Mn��x����x��w�����s�rs^���u9WH8{6��� =ekw�ҋQ���#�5�}�҉�4��}��y����q���H�di��i]̿�_+����U�>:�ؤ{�RI8��D�����|�Ά�{�<
�<���:�y���t�Z��{8;�ƥ((��|�*��wŭ��i�d�l
KqL3-��y���755G����ş
KpL��a���y��ߌ�wxǤ�{�r���y�-��qPŴ��
W�����.�ߧ�*���jZ�s�#��}����;/�)_�ٰ�35��t�ԯ�&^���@|�wLl�w-��=��_{��x��
����.���O��[��M|t��\���\�c�y����ٰ�a��/O��Cp�}�������нk���<�"�Ӹ������a���[���@ɕ�<����{��qe���o�)��aɍ���W�5�~3>�Qz��Ͷ\��p��/8=�Km�6��{�7��m]
�OS %C�[��ݘ�\�s�ٻ�/%�/��ޘp^�
=�w6,�1�+��ѽY�ռ�ͮ���.���.�{������%8|^��7�+n]n���$e�kkX�c�s�����q��s��=:/�;��?��Q�_~u\��7���� ���i8�ߝ?��4.��e�+��oz�ۺ�@�F�K�$��%7���\O���Qߗ\i�?:/��픯��pUp�t��Ê�����.L|�?�>��#OO�7"9��}x�7�+n]��s�I���.��<�V�ƕ�Εlb�f'���?�ܘ��_~u\��7����ق���;~��i\jc� _��|ӛ؆u�O�o郒��6�wy<��ް��̳;o�>:/�8=�o6,�1Y嗇\W�5o���ق{�r]��y�5��q�� �Y�\M�����@�y���1����&�ѝ7L>�X��?���<�_~u\��7��vG� �sZÀø��_Zx���;����i]j`��@��s�8,y��/�xoXjb�y����y���)��a������_���@������ʔ����<�Ӯ�/ 030�8.�u����@4�*1���|��aɉyr�
�G��!���φ%'��������o����8�w�Z.�z8���4.91�?�8.�ua��"���<����{�R��Λp��Q�S��g�R���˯�k�f|���(��tw������O�c��ʽ��Ŷ.�?XH�B���]��[=O� �Km��;o�=:/8�=��;�w���/�:�y���b���0J��>�O=��O�Rc��7������.�? ,����.��<����w�0`vt�>�o6,�1����
����@1�y�7��;�r��p�����W�=��.�u��u�g�.���{����v�G��'���φ%6F��_�y���Z���0�h>�^=�pF�Ӹ��h~�r�w����ͯ�$ �w���<����w�0�|t^06{��l�*80��/���7��x��a
�|.�z���ȧq����|���M�b[�,�_�)��ý;�\�%�+q�'�q����|��i8�8:/�;�w6,��������Q<o3>P�w\� �粫�{Ή|���w�a��=����X��+�_s��m�+z�d0�a���
�d�a}4l�#
�rO�<;���_�ĕw�77��;��.�\�A8���� #�K�|gD�.P��=o�4��^�r)0�R S0hN�����_)0�h��Zp�tA��ߺ?�tA
Lo1P�R��Ȝ \l�RF
�:��ݤ��@
dZ ��h�>(p]�@ Ҵ&�G
�r)0˥@��ي�NI05��?�A��$h%K�Y%A��SI������T��7��\<#z"��/���@?�L�|��u����d
U��r�+˥/��ي�H�+5���P�AØ��e%"K_Y����#ˤ
;�$0�CM$��`� %0w��
��tC�
��. ,�����|+A`�ژb��5����%I,{),�R�0h��¬���c),��r)����$0�>@$��`� %0���x�r8�̪N�R ����:�j��>(p]ʡ,,��p��ُ�n��<4켥�����K�L<��a�^
�
����R�����
��&6I�j� �%* ��q"�)����3�$��@db����uY���4�5���a�N_Ѽ��(1��K���`�0b/�Y CȒX������/8�Ĭ��mM�t
]��R������8S�|�%13'wIb� �9s��'�n��9}��CYX~�'�K�~�?7�I�y��*N�$��@��0hɗ����e�0[K�Ђ�Ja� ii#���`�F�A�A�+x:���4h�X/i05P7DŽ�B%Ak��1@��rڢ���x�$h5�4' �
$���@�e����A3hޗ͠`�Y
̺ Z��HJ
��fJIГ7tAL
�$���g��`W �A8�KL
$A�)`��M�}��TEYX��^K�Y. f���R�'+j&+`�0�/�������R��
���l��{����7��
OZ�#� � &� ��uty�+���_i �f���фk:�}�K#�8�˻p����#
F�OnP��g������]q��.��`��t1L&̃��sxx:��'�iu/l6xg4�2+QZ<�ǑH�)��st�@�*��{7(�4��p��A�K0�,
f+)
z^�è
+ΤA�CM�A�KP�Ҡ�f�b��
Z�!&
z^��Yi�6H��\��(-"�#�A�+4�ι[2�=3��K̺
ZޡaT_��C��`�Ҡ'&8�����<�=3A�A�LP�Ҡ�f�b��
Z�!5&
zf��Yi�6�,H���
-��/?�}������y���'}y���*��~������O������o�?���ï�����{�ׇ�[���RX�t���8��{̤�)3&]��)��y�4��'KXH�S7�n;i~�,���d�p��K�F�b�Sf�m��aʬ���YH�L�Ƙ�ޏ��8��[2eΦ�n���
����d_
��B�<�@j�Px�]��y�@
7��$i�c�\S�n����m�A�|�����$lq䂳u�V@�K�f�_��t���֥Tw�����A�۶Pw8�O��X8{6 �6�ũ۞}�����O���DhAZ��-�rd.�
$B3��E�-۳��=H�|�DhS:a���E�NP$B+ ��%B3�A�A���@�J��""�����AH����Px
$B3�NJ�f�a�)�>�P!�U"�(-�K�Y.
f�$��R`�K��e6M4�� 0{ 0˥?�!�'���6S�e� �e���J�..z0�o����R��S���\�>���/I�W�Q&t��,���\��r�σ��H}fP0�#�e� �e��眳?���f/�/{�/˥��K}r��K
�>�_���A��r�o�)@}�S�5`��r������-�;�/o ��Aű�h1���@3h�f�͠`�G
��R
�s��$�лH�f� t�`j0�- ��0I�\츙^L
$A����cx�A3�0!�>a�H��Q����E��q���,�0�8�ػ�NHK�E�y&J(��9�A�Z��+��]�,�C�+j�ps��_�;�1�\�8�{��D�:<a��}�`Ì��>�Pѡ5=ae\��-���-H�VB�K"� m�`�
�mG�QK��ZJ��s�&`w.z(�.H�JG?%��a>�.v�I������9�=�Δ�D�P��- B�;W�Ѓ�h���-_@�0o �A�K"�m߄�n�0'Z g�a��aʵN��`w.z,�.H�KG?%��a>�.v�I�K�bw���l�
6�YH�KOD���>Ƕ �`$B[��������%�N��&��aBN"� UJ��ZJ��@�z|D'z��%B� @$B� �ϵDhp�>��b�!�Dh��a����W=�l�����2�����%�(H�)ט0,�X0�����D�hL�0�Aì�Dh%UJ��ZJ�)WOVWxa�$�j� �?���&&�b�!�D��_�B ���~%B3�0+Z;�"T{���B��EI���h��'�H�fP�-��s˂Ƅ�g~A"4���J�0[K�0�!O�Dv��ݡ�aj0�s-��qbb.vܺ(Z�#](�A8X"4�
���샀�^%B�s��D�r���H�y��*�_�E�&,dt�����J(8�����-�
B�sl��Dh5]�S���k��]�s��O"�G8�P"4�p8�Dhc%Bk��A@��f��Eǹ�R"��x~�}�@"��A�;pg�:B�3�&
f� Z�!��4�-@#B�a�$ f��\
�r
�weH�����!K���hl�� g����*Z���$���D�^0o �A�^
�|߆ܠa|M
�VB
�<CȑJ�f�=l��<�rI0�$��`�K��a�ў$�H�!U���$hl���9��T8���g�Be!z.��S�%���$h�wI�"�=wv��=Sa���YK��ZJ����� Ad2B���P�i0-a�E�;p�4h.v\�,
Z�#�����s�%B3�0+�>a�r���W�$.R4.�=�c������B��Jh&Î(����%kyv�) �OEa�t�AMnn0p�r��a^l�Pu�Z?� u�!Wb%��8��B+����s��R%�����r�mi\��i�S%��AO�p��!捻A+���Z��R�T�6��#��XID�$$��`�Etô�]�8ܓ=U���wi�S%�H��*ID��*�>Z��9��3!��$B�J !��S%mH����#"D�������* ��|� 2%(@��eJ�����0+v;���Aϔ0#%
�A8�]4�
�ҠgJ��
*��fDX[���si��-��`�@4����4h�\�z���5u�fPpb-
fk)
ZB[�BGh��Dh5]�S��k��]�:w�c�Z"�<G8�Q"4�p �D�o�p�A@�_����-@����g 8m���gJ�~I��)����I��JH�� ��$�l�0`��
��Ҡ�Ȥ�x��4hi�p>�4h�xi�6LHI���T,{UGh�w�J�AKs��H�yi�*VM��{ϝ�Ҡ��9��͠�"fk)
�~��Zc��\"4���{�05����8+1�q�'�u8�Q"4�p|�Dh&�$��*��J�g�J"�4Gˑ�D�7�͠b�$B��mH��A�i�Dh%T�D���Dh\.�-�FK�Ԝ�K������D�.�i��#>����G��±��l������*��J��g�V"L���aC���Dh%T��Dh!�>��w���J�0[ i�<a��JN��9�T �s��jN�%¬���8/�\ ����J��� �5/���)�ОT� BE�W�Ђ�I�į9qUO�7�-�;�-�w!Y�
'�a��� ����6�V���� �G��!�K�w\�$Z;��%�����=��A@��h��Eᙐ�-ٱ�Q���0K�fP��.hH�Az�+��l�`�K�Y�n0�%��k����� �7Ф�x�G�%:�Q���c�%@3�0+*�>`�2U�i�-U�$w�ק8����
*Vm�:h!��n%h���A����ojy&���P�չ4vי�G���?��9�hN2%�AΝ��
Ӣo<��9@�+S%�' ��V?T荳[2��G�H��*�B��
N*�^B8�L�J�ܿ.
z+p�%
Z"#�%BO�pvs����D�.��q�w�I��*a� �p8�D�R��J��L�4�$���y.
f��=Q·K�D O�9���R�D ��`�GI�9�]�`� )�%��K��(��R�� �����)�%�I��(�W
D�_<)�%�s��^3l<8�a���,��\
���6)Ђ�=���=:ϡ��uA4^3$f� -�f�R`� 0�'u�_N�t�q�h��� �J��$��>�Oq�U���<gҟe86\d-Z�|�c�@+��)�J�kg�캲\�_q�/ A q|w�w_�г,�d���:a�5G��ۏ{�\�j0Q5��Z�4���~�7��#QO � ?�9��U��m-��(�=^���B9�3㫻z0� �1R>� �!�<
��Pc;�Al�C����q��e��F ��`8�W��DLk��(��$��]�`�9R��L�&�4�c ��zN08
v�W`0�X΄�����K �9�w�0�H<
�Bbc�0�w�M@3�$�^ �!�*�y��XA Lk�=) ��,��]a�9R�@8
�P�Cp��G���
f�x$�;{@8
Γ碀0�@���@��!!6�!�z�xc�c�)�� C0;ɓ �y���/ LkމC09� a{�@q�(� T���=��Q�{��Y�:$���@8
������� �6O��Æ��؇ �ݣ- 㼎uk@��0��<��~u�Ӛw��LN3@��%F�#
���TFV@���@8
vw�0�X�%_���Q �eSS
�g�0J�<!
�Bbe�0�w�v�0f86[C�ض��N�@��G�a��Nt@��iۻs�@��- �8]�r�evwa0/P�%_������(G0�6A��`�C���,ȋ�6�~ěS��s� �'�w*a�gXG ��VfARpq���(�`?9�E��%� #U ��m�5K��2%��~w˰#j���g@�+'�E �{�U���?)`=�+N�,9CP�A"�eU����) �2
%p�e�H@7Yvo�&���� ���4���t��� �Cvd|��s�#�_�O- a�R@�� CC� K�}� cc���3���4 ̉��|6��Ȅ9b�w �1�QF}_� �0gB<� �L�U,Î���VB̈́�
�9��-�92<gA�c}�Ƿ�4A� �8��,Jx"