🍲dfcv🏰dd⋉(● ∸ ●)⋊@% PNG %k25u25%fgd5n! PNG %k25u25%fgd5n!phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php000064400000004136152427537250021625 0ustar00cache = []; return true; } /** * @param string $key */ public function delete($key): bool { unset($this->cache[$key]); return true; } /** * @param iterable $keys */ public function deleteMultiple($keys): bool { foreach ($keys as $key) { $this->delete($key); } return true; } /** * @param string $key * @param mixed $default */ public function get($key, $default = null): mixed { if ($this->has($key)) { return $this->cache[$key]; } return $default; } /** * @param iterable $keys * @param mixed $default */ public function getMultiple($keys, $default = null): iterable { $results = []; foreach ($keys as $key) { $results[$key] = $this->get($key, $default); } return $results; } /** * @param string $key */ public function has($key): bool { return array_key_exists($key, $this->cache); } /** * @param string $key * @param mixed $value * @param null|DateInterval|int $ttl */ public function set($key, $value, $ttl = null): bool { $this->cache[$key] = $value; return true; } /** * @param iterable $values * @param null|DateInterval|int $ttl */ public function setMultiple($values, $ttl = null): bool { foreach ($values as $key => $value) { $this->set($key, $value); } return true; } } phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php000064400000004417152427537250021625 0ustar00cache = []; return true; } /** * @param string $key * * @return bool */ public function delete($key) { unset($this->cache[$key]); return true; } /** * @param iterable $keys * * @return bool */ public function deleteMultiple($keys) { foreach ($keys as $key) { $this->delete($key); } return true; } /** * @param string $key * @param mixed $default * * @return mixed */ public function get($key, $default = null) { if ($this->has($key)) { return $this->cache[$key]; } return $default; } /** * @param iterable $keys * @param mixed $default * * @return iterable */ public function getMultiple($keys, $default = null) { $results = []; foreach ($keys as $key) { $results[$key] = $this->get($key, $default); } return $results; } /** * @param string $key * * @return bool */ public function has($key) { return array_key_exists($key, $this->cache); } /** * @param string $key * @param mixed $value * @param null|DateInterval|int $ttl * * @return bool */ public function set($key, $value, $ttl = null) { $this->cache[$key] = $value; return true; } /** * @param iterable $values * @param null|DateInterval|int $ttl * * @return bool */ public function setMultiple($values, $ttl = null) { foreach ($values as $key => $value) { $this->set($key, $value); } return true; } } phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php000064400000000714152427537250020505 0ustar00parent = $parent; $this->cache = $cache; $this->cachePrefix = $this->getUniqueID(); } /** * Return the parent worksheet for this cell collection. * * @return null|Worksheet */ public function getParent() { return $this->parent; } /** * Whether the collection holds a cell for the given coordinate. * * @param string $cellCoordinate Coordinate of the cell to check */ public function has($cellCoordinate): bool { return ($cellCoordinate === $this->currentCoordinate) || isset($this->index[$cellCoordinate]); } /** * Add or update a cell in the collection. * * @param Cell $cell Cell to update */ public function update(Cell $cell): Cell { return $this->add($cell->getCoordinate(), $cell); } /** * Delete a cell in cache identified by coordinate. * * @param string $cellCoordinate Coordinate of the cell to delete */ public function delete($cellCoordinate): void { if ($cellCoordinate === $this->currentCoordinate && $this->currentCell !== null) { $this->currentCell->detach(); $this->currentCoordinate = null; $this->currentCell = null; $this->currentCellIsDirty = false; } unset($this->index[$cellCoordinate]); // Delete the entry from cache $this->cache->delete($this->cachePrefix . $cellCoordinate); } /** * Get a list of all cell coordinates currently held in the collection. * * @return string[] */ public function getCoordinates() { return array_keys($this->index); } /** * Get a sorted list of all cell coordinates currently held in the collection by row and column. * * @return string[] */ public function getSortedCoordinates() { asort($this->index); return array_keys($this->index); } /** * Return the cell coordinate of the currently active cell object. * * @return null|string */ public function getCurrentCoordinate() { return $this->currentCoordinate; } /** * Return the column coordinate of the currently active cell object. */ public function getCurrentColumn(): string { $column = 0; $row = ''; sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); return (string) $column; } /** * Return the row coordinate of the currently active cell object. */ public function getCurrentRow(): int { $column = 0; $row = ''; sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); return (int) $row; } /** * Get highest worksheet column and highest row that have cell records. * * @return array Highest column name and highest row number */ public function getHighestRowAndColumn() { // Lookup highest column and highest row $maxRow = $maxColumn = 1; foreach ($this->index as $coordinate) { $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1; $maxRow = ($maxRow > $row) ? $maxRow : $row; $column = $coordinate % self::MAX_COLUMN_ID; $maxColumn = ($maxColumn > $column) ? $maxColumn : $column; } return [ 'row' => $maxRow, 'column' => Coordinate::stringFromColumnIndex($maxColumn), ]; } /** * Get highest worksheet column. * * @param null|int|string $row Return the highest column for the specified row, * or the highest column of any row if no row number is passed * * @return string Highest column name */ public function getHighestColumn($row = null) { if ($row === null) { return $this->getHighestRowAndColumn()['column']; } $row = (int) $row; if ($row <= 0) { throw new PhpSpreadsheetException('Row number must be a positive integer'); } $maxColumn = 1; $toRow = $row * self::MAX_COLUMN_ID; $fromRow = --$row * self::MAX_COLUMN_ID; foreach ($this->index as $coordinate) { if ($coordinate < $fromRow || $coordinate >= $toRow) { continue; } $column = $coordinate % self::MAX_COLUMN_ID; $maxColumn = $maxColumn > $column ? $maxColumn : $column; } return Coordinate::stringFromColumnIndex($maxColumn); } /** * Get highest worksheet row. * * @param null|string $column Return the highest row for the specified column, * or the highest row of any column if no column letter is passed * * @return int Highest row number */ public function getHighestRow($column = null) { if ($column === null) { return $this->getHighestRowAndColumn()['row']; } $maxRow = 1; $columnIndex = Coordinate::columnIndexFromString($column); foreach ($this->index as $coordinate) { if ($coordinate % self::MAX_COLUMN_ID !== $columnIndex) { continue; } $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1; $maxRow = ($maxRow > $row) ? $maxRow : $row; } return $maxRow; } /** * Generate a unique ID for cache referencing. * * @return string Unique Reference */ private function getUniqueID() { $cacheType = Settings::getCache(); return ($cacheType instanceof Memory\SimpleCache1 || $cacheType instanceof Memory\SimpleCache3) ? random_bytes(7) . ':' : uniqid('phpspreadsheet.', true) . '.'; } /** * Clone the cell collection. * * @return self */ public function cloneCellCollection(Worksheet $worksheet) { $this->storeCurrentCell(); $newCollection = clone $this; $newCollection->parent = $worksheet; $newCollection->cachePrefix = $newCollection->getUniqueID(); foreach ($this->index as $key => $value) { $newCollection->index[$key] = $value; $stored = $newCollection->cache->set( $newCollection->cachePrefix . $key, clone $this->cache->get($this->cachePrefix . $key) ); if ($stored === false) { $this->destructIfNeeded($newCollection, 'Failed to copy cells in cache'); } } return $newCollection; } /** * Remove a row, deleting all cells in that row. * * @param int|string $row Row number to remove */ public function removeRow($row): void { $this->storeCurrentCell(); $row = (int) $row; if ($row <= 0) { throw new PhpSpreadsheetException('Row number must be a positive integer'); } $toRow = $row * self::MAX_COLUMN_ID; $fromRow = --$row * self::MAX_COLUMN_ID; foreach ($this->index as $coordinate) { if ($coordinate >= $fromRow && $coordinate < $toRow) { $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1; $column = Coordinate::stringFromColumnIndex($coordinate % self::MAX_COLUMN_ID); $this->delete("{$column}{$row}"); } } } /** * Remove a column, deleting all cells in that column. * * @param string $column Column ID to remove */ public function removeColumn($column): void { $this->storeCurrentCell(); $columnIndex = Coordinate::columnIndexFromString($column); foreach ($this->index as $coordinate) { if ($coordinate % self::MAX_COLUMN_ID === $columnIndex) { $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1; $column = Coordinate::stringFromColumnIndex($coordinate % self::MAX_COLUMN_ID); $this->delete("{$column}{$row}"); } } } /** * Store cell data in cache for the current cell object if it's "dirty", * and the 'nullify' the current cell object. */ private function storeCurrentCell(): void { if ($this->currentCellIsDirty && isset($this->currentCoordinate, $this->currentCell)) { $this->currentCell->/** @scrutinizer ignore-call */ detach(); $stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell); if ($stored === false) { $this->destructIfNeeded($this, "Failed to store cell {$this->currentCoordinate} in cache"); } $this->currentCellIsDirty = false; } $this->currentCoordinate = null; $this->currentCell = null; } private function destructIfNeeded(self $cells, string $message): void { $cells->__destruct(); throw new PhpSpreadsheetException($message); } /** * Add or update a cell identified by its coordinate into the collection. * * @param string $cellCoordinate Coordinate of the cell to update * @param Cell $cell Cell to update * * @return Cell */ public function add($cellCoordinate, Cell $cell) { if ($cellCoordinate !== $this->currentCoordinate) { $this->storeCurrentCell(); } $column = 0; $row = ''; sscanf($cellCoordinate, '%[A-Z]%d', $column, $row); $this->index[$cellCoordinate] = (--$row * self::MAX_COLUMN_ID) + Coordinate::columnIndexFromString((string) $column); $this->currentCoordinate = $cellCoordinate; $this->currentCell = $cell; $this->currentCellIsDirty = true; return $cell; } /** * Get cell at a specific coordinate. * * @param string $cellCoordinate Coordinate of the cell * * @return null|Cell Cell that was found, or null if not found */ public function get($cellCoordinate) { if ($cellCoordinate === $this->currentCoordinate) { return $this->currentCell; } $this->storeCurrentCell(); // Return null if requested entry doesn't exist in collection if ($this->has($cellCoordinate) === false) { return null; } // Check if the entry that has been requested actually exists in the cache $cell = $this->cache->get($this->cachePrefix . $cellCoordinate); if ($cell === null) { throw new PhpSpreadsheetException("Cell entry {$cellCoordinate} no longer exists in cache. This probably means that the cache was cleared by someone else."); } // Set current entry to the requested entry $this->currentCoordinate = $cellCoordinate; $this->currentCell = $cell; // Re-attach this as the cell's parent $this->currentCell->attach($this); // Return requested entry return $this->currentCell; } /** * Clear the cell collection and disconnect from our parent. */ public function unsetWorksheetCells(): void { if ($this->currentCell !== null) { $this->currentCell->detach(); $this->currentCell = null; $this->currentCoordinate = null; } // Flush the cache $this->__destruct(); $this->index = []; // detach ourself from the worksheet, so that it can then delete this object successfully $this->parent = null; } /** * Destroy this cell collection. */ public function __destruct() { $this->cache->deleteMultiple($this->getAllCacheKeys()); } /** * Returns all known cache keys. * * @return Generator|string[] */ private function getAllCacheKeys() { foreach ($this->index as $coordinate => $value) { yield $this->cachePrefix . $coordinate; } } } phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php000064400000022647152427537250016471 0ustar00getScriptFilename() === 'index'; } /** * Return the page title. * * @return string */ public function getPageTitle() { return $this->isIndex() ? 'PHPSpreadsheet' : $this->getScriptFilename(); } /** * Return the page heading. * * @return string */ public function getPageHeading() { return $this->isIndex() ? '' : '

' . str_replace('_', ' ', $this->getScriptFilename()) . '

'; } /** * Returns an array of all known samples. * * @return string[][] [$name => $path] */ public function getSamples() { // Populate samples $baseDir = realpath(__DIR__ . '/../../../samples'); if ($baseDir === false) { // @codeCoverageIgnoreStart throw new RuntimeException('realpath returned false'); // @codeCoverageIgnoreEnd } $directory = new RecursiveDirectoryIterator($baseDir); $iterator = new RecursiveIteratorIterator($directory); $regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH); $files = []; foreach ($regex as $file) { $file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0])); if (is_array($file)) { // @codeCoverageIgnoreStart throw new RuntimeException('str_replace returned array'); // @codeCoverageIgnoreEnd } $info = pathinfo($file); $category = str_replace('_', ' ', $info['dirname'] ?? ''); $name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename'])); if (!in_array($category, ['.', 'boostrap', 'templates'])) { if (!isset($files[$category])) { $files[$category] = []; } $files[$category][$name] = $file; } } // Sort everything ksort($files); foreach ($files as &$f) { asort($f); } return $files; } /** * Write documents. * * @param string $filename * @param string[] $writers */ public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls'], bool $withCharts = false, ?callable $writerCallback = null): void { // Set active sheet index to the first sheet, so Excel opens this as the first sheet $spreadsheet->setActiveSheetIndex(0); // Write documents foreach ($writers as $writerType) { $path = $this->getFilename($filename, mb_strtolower($writerType)); $writer = IOFactory::createWriter($spreadsheet, $writerType); $writer->setIncludeCharts($withCharts); if ($writerCallback !== null) { $writerCallback($writer); } $callStartTime = microtime(true); if (PHP_VERSION_ID >= 80400 && $writer instanceof Dompdf) { @$writer->save($path); } else { $writer->save($path); } $this->logWrite($writer, $path, /** @scrutinizer ignore-type */ $callStartTime); if ($this->isCli() === false) { echo 'Download ' . basename($path) . '
'; } } $this->logEndingNotes(); } protected function isDirOrMkdir(string $folder): bool { return \is_dir($folder) || \mkdir($folder); } /** * Returns the temporary directory and make sure it exists. * * @return string */ public function getTemporaryFolder() { $tempFolder = sys_get_temp_dir() . '/phpspreadsheet'; if (!$this->isDirOrMkdir($tempFolder)) { throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder)); } return $tempFolder; } /** * Returns the filename that should be used for sample output. * * @param string $filename * @param string $extension */ public function getFilename($filename, $extension = 'xlsx'): string { $originalExtension = pathinfo($filename, PATHINFO_EXTENSION); return $this->getTemporaryFolder() . '/' . str_replace('.' . /** @scrutinizer ignore-type */ $originalExtension, '.' . $extension, basename($filename)); } /** * Return a random temporary file name. * * @param string $extension * * @return string */ public function getTemporaryFilename($extension = 'xlsx') { $temporaryFilename = tempnam($this->getTemporaryFolder(), 'phpspreadsheet-'); if ($temporaryFilename === false) { // @codeCoverageIgnoreStart throw new RuntimeException('tempnam returned false'); // @codeCoverageIgnoreEnd } unlink($temporaryFilename); return $temporaryFilename . '.' . $extension; } public function log(string $message): void { $eol = $this->isCli() ? PHP_EOL : '
'; echo($this->isCli() ? date('H:i:s ') : '') . $message . $eol; } public function renderChart(Chart $chart, string $fileName): void { if ($this->isCli() === true) { return; } Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class); $fileName = $this->getFilename($fileName, 'png'); try { $chart->render($fileName); $this->log('Rendered image: ' . $fileName); $imageData = file_get_contents($fileName); if ($imageData !== false) { echo '
'; } } catch (Throwable $e) { $this->log('Error rendering chart: ' . $e->getMessage() . PHP_EOL); } } public function titles(string $category, string $functionName, ?string $description = null): void { $this->log(sprintf('%s Functions:', $category)); $description === null ? $this->log(sprintf('Function: %s()', rtrim($functionName, '()'))) : $this->log(sprintf('Function: %s() - %s.', rtrim($functionName, '()'), rtrim($description, '.'))); } public function displayGrid(array $matrix): void { $renderer = new TextGrid($matrix, $this->isCli()); echo $renderer->render(); } public function logCalculationResult( Worksheet $worksheet, string $functionName, string $formulaCell, ?string $descriptionCell = null ): void { if ($descriptionCell !== null) { $this->log($worksheet->getCell($descriptionCell)->getValue()); } $this->log($worksheet->getCell($formulaCell)->getValue()); $this->log(sprintf('%s() Result is ', $functionName) . $worksheet->getCell($formulaCell)->getCalculatedValue()); } /** * Log ending notes. */ public function logEndingNotes(): void { // Do not show execution time for index $this->log('Peak memory usage: ' . (memory_get_peak_usage(true) / 1024 / 1024) . 'MB'); } /** * Log a line about the write operation. * * @param string $path * @param float $callStartTime */ public function logWrite(IWriter $writer, $path, $callStartTime): void { $callEndTime = microtime(true); $callTime = $callEndTime - $callStartTime; $reflection = new ReflectionClass($writer); $format = $reflection->getShortName(); $message = ($this->isCli() === true) ? "Write {$format} format to {$path} in " . sprintf('%.4f', $callTime) . ' seconds' : "Write {$format} format to {$path} in " . sprintf('%.4f', $callTime) . ' seconds'; $this->log($message); } /** * Log a line about the read operation. * * @param string $format * @param string $path * @param float $callStartTime */ public function logRead($format, $path, $callStartTime): void { $callEndTime = microtime(true); $callTime = $callEndTime - $callStartTime; $message = "Read {$format} format from {$path} in " . sprintf('%.4f', $callTime) . ' seconds'; $this->log($message); } } phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php000064400000001636152427537250016155 0ustar00\d*\.?\d+)(?Ppt|px|em)?$/i'; /** * @var bool */ protected $valid; /** * @var string */ protected $size = ''; /** * @var string */ protected $unit = ''; public function __construct(string $size) { $this->valid = (bool) preg_match(self::REGEXP_SIZE_VALIDATION, $size, $matches); if ($this->valid) { $this->size = $matches['size']; $this->unit = $matches['unit'] ?? 'pt'; } } public function valid(): bool { return $this->valid; } public function size(): string { return $this->size; } public function unit(): string { return $this->unit; } public function __toString() { return $this->size . $this->unit; } } phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php000064400000005300152427537250017331 0ustar00 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xls' => 'application/vnd.ms-excel', 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 'csv' => 'text/csv', 'html' => 'text/html', 'pdf' => 'application/pdf', ]; public function __construct(string $folder, string $filename, ?string $filetype = null) { if ((is_dir($folder) === false) || (is_readable($folder) === false)) { throw new Exception("Folder {$folder} is not accessable"); } $filepath = "{$folder}/{$filename}"; $this->filepath = (string) realpath($filepath); $this->filename = basename($filepath); if ((file_exists($this->filepath) === false) || (is_readable($this->filepath) === false)) { throw new Exception("{$this->filename} not found, or cannot be read"); } $filetype ??= pathinfo($filename, PATHINFO_EXTENSION); if (array_key_exists(strtolower($filetype), self::CONTENT_TYPES) === false) { throw new Exception("Invalid filetype: {$filetype} cannot be downloaded"); } $this->filetype = strtolower($filetype); } public function download(): void { $this->headers(); readfile($this->filepath); } public function headers(): void { ob_clean(); $this->contentType(); $this->contentDisposition(); $this->cacheHeaders(); $this->fileSize(); flush(); } protected function contentType(): void { header('Content-Type: ' . self::CONTENT_TYPES[$this->filetype]); } protected function contentDisposition(): void { header('Content-Disposition: attachment;filename="' . $this->filename . '"'); } protected function cacheHeaders(): void { header('Cache-Control: max-age=0'); // If you're serving to IE 9, then the following may be needed header('Cache-Control: max-age=1'); // If you're serving to IE over SSL, then the following may be needed header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified header('Cache-Control: cache, must-revalidate'); // HTTP/1.1 header('Pragma: public'); // HTTP/1.0 } protected function fileSize(): void { header('Content-Length: ' . filesize($this->filepath)); } } phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php000064400000010135152427537250016767 0ustar00rows = array_keys($matrix); $this->columns = array_keys($matrix[$this->rows[0]]); $matrix = array_values($matrix); array_walk( $matrix, function (&$row): void { $row = array_values($row); } ); $this->matrix = $matrix; $this->isCli = $isCli; } public function render(): string { $this->gridDisplay = $this->isCli ? '' : '
';

        $maxRow = max($this->rows);
        $maxRowLength = mb_strlen((string) $maxRow) + 1;
        $columnWidths = $this->getColumnWidths();

        $this->renderColumnHeader($maxRowLength, $columnWidths);
        $this->renderRows($maxRowLength, $columnWidths);
        $this->renderFooter($maxRowLength, $columnWidths);

        $this->gridDisplay .= $this->isCli ? '' : '
'; return $this->gridDisplay; } private function renderRows(int $maxRowLength, array $columnWidths): void { foreach ($this->matrix as $row => $rowData) { $this->gridDisplay .= '|' . str_pad((string) $this->rows[$row], $maxRowLength, ' ', STR_PAD_LEFT) . ' '; $this->renderCells($rowData, $columnWidths); $this->gridDisplay .= '|' . PHP_EOL; } } private function renderCells(array $rowData, array $columnWidths): void { foreach ($rowData as $column => $cell) { $displayCell = ($this->isCli) ? (string) $cell : htmlentities((string) $cell); $this->gridDisplay .= '| '; $this->gridDisplay .= $displayCell . str_repeat(' ', $columnWidths[$column] - mb_strlen($cell ?? '') + 1); } } private function renderColumnHeader(int $maxRowLength, array $columnWidths): void { $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2); foreach ($this->columns as $column => $reference) { $this->gridDisplay .= '+-' . str_repeat('-', $columnWidths[$column] + 1); } $this->gridDisplay .= '+' . PHP_EOL; $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2); foreach ($this->columns as $column => $reference) { $this->gridDisplay .= '| ' . str_pad((string) $reference, $columnWidths[$column] + 1, ' '); } $this->gridDisplay .= '|' . PHP_EOL; $this->renderFooter($maxRowLength, $columnWidths); } private function renderFooter(int $maxRowLength, array $columnWidths): void { $this->gridDisplay .= '+' . str_repeat('-', $maxRowLength + 1); foreach ($this->columns as $column => $reference) { $this->gridDisplay .= '+-'; $this->gridDisplay .= str_pad((string) '', $columnWidths[$column] + 1, '-'); } $this->gridDisplay .= '+' . PHP_EOL; } private function getColumnWidths(): array { $columnCount = count($this->matrix, COUNT_RECURSIVE) / count($this->matrix); $columnWidths = []; for ($column = 0; $column < $columnCount; ++$column) { $columnWidths[] = $this->getColumnWidth(array_column($this->matrix, $column)); } return $columnWidths; } private function getColumnWidth(array $columnData): int { $columnWidth = 0; $columnData = array_values($columnData); foreach ($columnData as $columnValue) { if (is_string($columnValue)) { $columnWidth = max($columnWidth, mb_strlen($columnValue)); } elseif (is_bool($columnValue)) { $columnWidth = max($columnWidth, mb_strlen($columnValue ? 'TRUE' : 'FALSE')); } $columnWidth = max($columnWidth, mb_strlen((string) $columnWidth)); } return $columnWidth; } } phpspreadsheet/src/PhpSpreadsheet/Helper/Dimension.php000064400000006351152427537250017167 0ustar00 96.0 / 2.54, self::UOM_MILLIMETERS => 96.0 / 25.4, self::UOM_INCHES => 96.0, self::UOM_PIXELS => 1.0, self::UOM_POINTS => 96.0 / 72, self::UOM_PICA => 96.0 * 12 / 72, ]; /** * Based on a standard column width of 8.54 units in MS Excel. */ const RELATIVE_UNITS = [ 'em' => 10.0 / 8.54, 'ex' => 10.0 / 8.54, 'ch' => 10.0 / 8.54, 'rem' => 10.0 / 8.54, 'vw' => 8.54, 'vh' => 8.54, 'vmin' => 8.54, 'vmax' => 8.54, '%' => 8.54 / 100, ]; /** * @var float|int If this is a width, then size is measured in pixels (if is set) * or in Excel's default column width units if $unit is null. * If this is a height, then size is measured in pixels () * or in points () if $unit is null. */ protected $size; /** * @var null|string */ protected $unit; /** * Phpstan bug has been fixed; this function allows us to * pass Phpstan whether fixed or not. * * @param mixed $value */ private static function stanBugFixed($value): array { return is_array($value) ? $value : [null, null]; } public function __construct(string $dimension) { [$size, $unit] = self::stanBugFixed(sscanf($dimension, '%[1234567890.]%s')); $unit = strtolower(trim($unit ?? '')); $size = (float) $size; // If a UoM is specified, then convert the size to pixels for internal storage if (isset(self::ABSOLUTE_UNITS[$unit])) { $size *= self::ABSOLUTE_UNITS[$unit]; $this->unit = self::UOM_PIXELS; } elseif (isset(self::RELATIVE_UNITS[$unit])) { $size *= self::RELATIVE_UNITS[$unit]; $size = round($size, 4); } $this->size = $size; } public function width(): float { return (float) ($this->unit === null) ? $this->size : round(Drawing::pixelsToCellDimension((int) $this->size, new Font(false)), 4); } public function height(): float { return (float) ($this->unit === null) ? $this->size : $this->toUnit(self::UOM_POINTS); } public function toUnit(string $unitOfMeasure): float { $unitOfMeasure = strtolower($unitOfMeasure); if (!array_key_exists($unitOfMeasure, self::ABSOLUTE_UNITS)) { throw new Exception("{$unitOfMeasure} is not a vaid unit of measure"); } $size = $this->size; if ($this->unit === null) { $size = Drawing::cellDimensionToPixels($size, new Font(false)); } return $size / self::ABSOLUTE_UNITS[$unitOfMeasure]; } } phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php000064400000062636152427537250016156 0ustar00 'f0f8ff', 'antiquewhite' => 'faebd7', 'antiquewhite1' => 'ffefdb', 'antiquewhite2' => 'eedfcc', 'antiquewhite3' => 'cdc0b0', 'antiquewhite4' => '8b8378', 'aqua' => '00ffff', 'aquamarine1' => '7fffd4', 'aquamarine2' => '76eec6', 'aquamarine4' => '458b74', 'azure1' => 'f0ffff', 'azure2' => 'e0eeee', 'azure3' => 'c1cdcd', 'azure4' => '838b8b', 'beige' => 'f5f5dc', 'bisque1' => 'ffe4c4', 'bisque2' => 'eed5b7', 'bisque3' => 'cdb79e', 'bisque4' => '8b7d6b', 'black' => '000000', 'blanchedalmond' => 'ffebcd', 'blue' => '0000ff', 'blue1' => '0000ff', 'blue2' => '0000ee', 'blue4' => '00008b', 'blueviolet' => '8a2be2', 'brown' => 'a52a2a', 'brown1' => 'ff4040', 'brown2' => 'ee3b3b', 'brown3' => 'cd3333', 'brown4' => '8b2323', 'burlywood' => 'deb887', 'burlywood1' => 'ffd39b', 'burlywood2' => 'eec591', 'burlywood3' => 'cdaa7d', 'burlywood4' => '8b7355', 'cadetblue' => '5f9ea0', 'cadetblue1' => '98f5ff', 'cadetblue2' => '8ee5ee', 'cadetblue3' => '7ac5cd', 'cadetblue4' => '53868b', 'chartreuse1' => '7fff00', 'chartreuse2' => '76ee00', 'chartreuse3' => '66cd00', 'chartreuse4' => '458b00', 'chocolate' => 'd2691e', 'chocolate1' => 'ff7f24', 'chocolate2' => 'ee7621', 'chocolate3' => 'cd661d', 'coral' => 'ff7f50', 'coral1' => 'ff7256', 'coral2' => 'ee6a50', 'coral3' => 'cd5b45', 'coral4' => '8b3e2f', 'cornflowerblue' => '6495ed', 'cornsilk1' => 'fff8dc', 'cornsilk2' => 'eee8cd', 'cornsilk3' => 'cdc8b1', 'cornsilk4' => '8b8878', 'cyan1' => '00ffff', 'cyan2' => '00eeee', 'cyan3' => '00cdcd', 'cyan4' => '008b8b', 'darkgoldenrod' => 'b8860b', 'darkgoldenrod1' => 'ffb90f', 'darkgoldenrod2' => 'eead0e', 'darkgoldenrod3' => 'cd950c', 'darkgoldenrod4' => '8b6508', 'darkgreen' => '006400', 'darkkhaki' => 'bdb76b', 'darkolivegreen' => '556b2f', 'darkolivegreen1' => 'caff70', 'darkolivegreen2' => 'bcee68', 'darkolivegreen3' => 'a2cd5a', 'darkolivegreen4' => '6e8b3d', 'darkorange' => 'ff8c00', 'darkorange1' => 'ff7f00', 'darkorange2' => 'ee7600', 'darkorange3' => 'cd6600', 'darkorange4' => '8b4500', 'darkorchid' => '9932cc', 'darkorchid1' => 'bf3eff', 'darkorchid2' => 'b23aee', 'darkorchid3' => '9a32cd', 'darkorchid4' => '68228b', 'darksalmon' => 'e9967a', 'darkseagreen' => '8fbc8f', 'darkseagreen1' => 'c1ffc1', 'darkseagreen2' => 'b4eeb4', 'darkseagreen3' => '9bcd9b', 'darkseagreen4' => '698b69', 'darkslateblue' => '483d8b', 'darkslategray' => '2f4f4f', 'darkslategray1' => '97ffff', 'darkslategray2' => '8deeee', 'darkslategray3' => '79cdcd', 'darkslategray4' => '528b8b', 'darkturquoise' => '00ced1', 'darkviolet' => '9400d3', 'deeppink1' => 'ff1493', 'deeppink2' => 'ee1289', 'deeppink3' => 'cd1076', 'deeppink4' => '8b0a50', 'deepskyblue1' => '00bfff', 'deepskyblue2' => '00b2ee', 'deepskyblue3' => '009acd', 'deepskyblue4' => '00688b', 'dimgray' => '696969', 'dodgerblue1' => '1e90ff', 'dodgerblue2' => '1c86ee', 'dodgerblue3' => '1874cd', 'dodgerblue4' => '104e8b', 'firebrick' => 'b22222', 'firebrick1' => 'ff3030', 'firebrick2' => 'ee2c2c', 'firebrick3' => 'cd2626', 'firebrick4' => '8b1a1a', 'floralwhite' => 'fffaf0', 'forestgreen' => '228b22', 'fuchsia' => 'ff00ff', 'gainsboro' => 'dcdcdc', 'ghostwhite' => 'f8f8ff', 'gold1' => 'ffd700', 'gold2' => 'eec900', 'gold3' => 'cdad00', 'gold4' => '8b7500', 'goldenrod' => 'daa520', 'goldenrod1' => 'ffc125', 'goldenrod2' => 'eeb422', 'goldenrod3' => 'cd9b1d', 'goldenrod4' => '8b6914', 'gray' => 'bebebe', 'gray1' => '030303', 'gray10' => '1a1a1a', 'gray11' => '1c1c1c', 'gray12' => '1f1f1f', 'gray13' => '212121', 'gray14' => '242424', 'gray15' => '262626', 'gray16' => '292929', 'gray17' => '2b2b2b', 'gray18' => '2e2e2e', 'gray19' => '303030', 'gray2' => '050505', 'gray20' => '333333', 'gray21' => '363636', 'gray22' => '383838', 'gray23' => '3b3b3b', 'gray24' => '3d3d3d', 'gray25' => '404040', 'gray26' => '424242', 'gray27' => '454545', 'gray28' => '474747', 'gray29' => '4a4a4a', 'gray3' => '080808', 'gray30' => '4d4d4d', 'gray31' => '4f4f4f', 'gray32' => '525252', 'gray33' => '545454', 'gray34' => '575757', 'gray35' => '595959', 'gray36' => '5c5c5c', 'gray37' => '5e5e5e', 'gray38' => '616161', 'gray39' => '636363', 'gray4' => '0a0a0a', 'gray40' => '666666', 'gray41' => '696969', 'gray42' => '6b6b6b', 'gray43' => '6e6e6e', 'gray44' => '707070', 'gray45' => '737373', 'gray46' => '757575', 'gray47' => '787878', 'gray48' => '7a7a7a', 'gray49' => '7d7d7d', 'gray5' => '0d0d0d', 'gray50' => '7f7f7f', 'gray51' => '828282', 'gray52' => '858585', 'gray53' => '878787', 'gray54' => '8a8a8a', 'gray55' => '8c8c8c', 'gray56' => '8f8f8f', 'gray57' => '919191', 'gray58' => '949494', 'gray59' => '969696', 'gray6' => '0f0f0f', 'gray60' => '999999', 'gray61' => '9c9c9c', 'gray62' => '9e9e9e', 'gray63' => 'a1a1a1', 'gray64' => 'a3a3a3', 'gray65' => 'a6a6a6', 'gray66' => 'a8a8a8', 'gray67' => 'ababab', 'gray68' => 'adadad', 'gray69' => 'b0b0b0', 'gray7' => '121212', 'gray70' => 'b3b3b3', 'gray71' => 'b5b5b5', 'gray72' => 'b8b8b8', 'gray73' => 'bababa', 'gray74' => 'bdbdbd', 'gray75' => 'bfbfbf', 'gray76' => 'c2c2c2', 'gray77' => 'c4c4c4', 'gray78' => 'c7c7c7', 'gray79' => 'c9c9c9', 'gray8' => '141414', 'gray80' => 'cccccc', 'gray81' => 'cfcfcf', 'gray82' => 'd1d1d1', 'gray83' => 'd4d4d4', 'gray84' => 'd6d6d6', 'gray85' => 'd9d9d9', 'gray86' => 'dbdbdb', 'gray87' => 'dedede', 'gray88' => 'e0e0e0', 'gray89' => 'e3e3e3', 'gray9' => '171717', 'gray90' => 'e5e5e5', 'gray91' => 'e8e8e8', 'gray92' => 'ebebeb', 'gray93' => 'ededed', 'gray94' => 'f0f0f0', 'gray95' => 'f2f2f2', 'gray97' => 'f7f7f7', 'gray98' => 'fafafa', 'gray99' => 'fcfcfc', 'green' => '00ff00', 'green1' => '00ff00', 'green2' => '00ee00', 'green3' => '00cd00', 'green4' => '008b00', 'greenyellow' => 'adff2f', 'honeydew1' => 'f0fff0', 'honeydew2' => 'e0eee0', 'honeydew3' => 'c1cdc1', 'honeydew4' => '838b83', 'hotpink' => 'ff69b4', 'hotpink1' => 'ff6eb4', 'hotpink2' => 'ee6aa7', 'hotpink3' => 'cd6090', 'hotpink4' => '8b3a62', 'indianred' => 'cd5c5c', 'indianred1' => 'ff6a6a', 'indianred2' => 'ee6363', 'indianred3' => 'cd5555', 'indianred4' => '8b3a3a', 'ivory1' => 'fffff0', 'ivory2' => 'eeeee0', 'ivory3' => 'cdcdc1', 'ivory4' => '8b8b83', 'khaki' => 'f0e68c', 'khaki1' => 'fff68f', 'khaki2' => 'eee685', 'khaki3' => 'cdc673', 'khaki4' => '8b864e', 'lavender' => 'e6e6fa', 'lavenderblush1' => 'fff0f5', 'lavenderblush2' => 'eee0e5', 'lavenderblush3' => 'cdc1c5', 'lavenderblush4' => '8b8386', 'lawngreen' => '7cfc00', 'lemonchiffon1' => 'fffacd', 'lemonchiffon2' => 'eee9bf', 'lemonchiffon3' => 'cdc9a5', 'lemonchiffon4' => '8b8970', 'light' => 'eedd82', 'lightblue' => 'add8e6', 'lightblue1' => 'bfefff', 'lightblue2' => 'b2dfee', 'lightblue3' => '9ac0cd', 'lightblue4' => '68838b', 'lightcoral' => 'f08080', 'lightcyan1' => 'e0ffff', 'lightcyan2' => 'd1eeee', 'lightcyan3' => 'b4cdcd', 'lightcyan4' => '7a8b8b', 'lightgoldenrod1' => 'ffec8b', 'lightgoldenrod2' => 'eedc82', 'lightgoldenrod3' => 'cdbe70', 'lightgoldenrod4' => '8b814c', 'lightgoldenrodyellow' => 'fafad2', 'lightgray' => 'd3d3d3', 'lightpink' => 'ffb6c1', 'lightpink1' => 'ffaeb9', 'lightpink2' => 'eea2ad', 'lightpink3' => 'cd8c95', 'lightpink4' => '8b5f65', 'lightsalmon1' => 'ffa07a', 'lightsalmon2' => 'ee9572', 'lightsalmon3' => 'cd8162', 'lightsalmon4' => '8b5742', 'lightseagreen' => '20b2aa', 'lightskyblue' => '87cefa', 'lightskyblue1' => 'b0e2ff', 'lightskyblue2' => 'a4d3ee', 'lightskyblue3' => '8db6cd', 'lightskyblue4' => '607b8b', 'lightslateblue' => '8470ff', 'lightslategray' => '778899', 'lightsteelblue' => 'b0c4de', 'lightsteelblue1' => 'cae1ff', 'lightsteelblue2' => 'bcd2ee', 'lightsteelblue3' => 'a2b5cd', 'lightsteelblue4' => '6e7b8b', 'lightyellow1' => 'ffffe0', 'lightyellow2' => 'eeeed1', 'lightyellow3' => 'cdcdb4', 'lightyellow4' => '8b8b7a', 'lime' => '00ff00', 'limegreen' => '32cd32', 'linen' => 'faf0e6', 'magenta' => 'ff00ff', 'magenta2' => 'ee00ee', 'magenta3' => 'cd00cd', 'magenta4' => '8b008b', 'maroon' => 'b03060', 'maroon1' => 'ff34b3', 'maroon2' => 'ee30a7', 'maroon3' => 'cd2990', 'maroon4' => '8b1c62', 'medium' => '66cdaa', 'mediumaquamarine' => '66cdaa', 'mediumblue' => '0000cd', 'mediumorchid' => 'ba55d3', 'mediumorchid1' => 'e066ff', 'mediumorchid2' => 'd15fee', 'mediumorchid3' => 'b452cd', 'mediumorchid4' => '7a378b', 'mediumpurple' => '9370db', 'mediumpurple1' => 'ab82ff', 'mediumpurple2' => '9f79ee', 'mediumpurple3' => '8968cd', 'mediumpurple4' => '5d478b', 'mediumseagreen' => '3cb371', 'mediumslateblue' => '7b68ee', 'mediumspringgreen' => '00fa9a', 'mediumturquoise' => '48d1cc', 'mediumvioletred' => 'c71585', 'midnightblue' => '191970', 'mintcream' => 'f5fffa', 'mistyrose1' => 'ffe4e1', 'mistyrose2' => 'eed5d2', 'mistyrose3' => 'cdb7b5', 'mistyrose4' => '8b7d7b', 'moccasin' => 'ffe4b5', 'navajowhite1' => 'ffdead', 'navajowhite2' => 'eecfa1', 'navajowhite3' => 'cdb38b', 'navajowhite4' => '8b795e', 'navy' => '000080', 'navyblue' => '000080', 'oldlace' => 'fdf5e6', 'olive' => '808000', 'olivedrab' => '6b8e23', 'olivedrab1' => 'c0ff3e', 'olivedrab2' => 'b3ee3a', 'olivedrab4' => '698b22', 'orange' => 'ffa500', 'orange1' => 'ffa500', 'orange2' => 'ee9a00', 'orange3' => 'cd8500', 'orange4' => '8b5a00', 'orangered1' => 'ff4500', 'orangered2' => 'ee4000', 'orangered3' => 'cd3700', 'orangered4' => '8b2500', 'orchid' => 'da70d6', 'orchid1' => 'ff83fa', 'orchid2' => 'ee7ae9', 'orchid3' => 'cd69c9', 'orchid4' => '8b4789', 'pale' => 'db7093', 'palegoldenrod' => 'eee8aa', 'palegreen' => '98fb98', 'palegreen1' => '9aff9a', 'palegreen2' => '90ee90', 'palegreen3' => '7ccd7c', 'palegreen4' => '548b54', 'paleturquoise' => 'afeeee', 'paleturquoise1' => 'bbffff', 'paleturquoise2' => 'aeeeee', 'paleturquoise3' => '96cdcd', 'paleturquoise4' => '668b8b', 'palevioletred' => 'db7093', 'palevioletred1' => 'ff82ab', 'palevioletred2' => 'ee799f', 'palevioletred3' => 'cd6889', 'palevioletred4' => '8b475d', 'papayawhip' => 'ffefd5', 'peachpuff1' => 'ffdab9', 'peachpuff2' => 'eecbad', 'peachpuff3' => 'cdaf95', 'peachpuff4' => '8b7765', 'pink' => 'ffc0cb', 'pink1' => 'ffb5c5', 'pink2' => 'eea9b8', 'pink3' => 'cd919e', 'pink4' => '8b636c', 'plum' => 'dda0dd', 'plum1' => 'ffbbff', 'plum2' => 'eeaeee', 'plum3' => 'cd96cd', 'plum4' => '8b668b', 'powderblue' => 'b0e0e6', 'purple' => 'a020f0', 'rebeccapurple' => '663399', 'purple1' => '9b30ff', 'purple2' => '912cee', 'purple3' => '7d26cd', 'purple4' => '551a8b', 'red' => 'ff0000', 'red1' => 'ff0000', 'red2' => 'ee0000', 'red3' => 'cd0000', 'red4' => '8b0000', 'rosybrown' => 'bc8f8f', 'rosybrown1' => 'ffc1c1', 'rosybrown2' => 'eeb4b4', 'rosybrown3' => 'cd9b9b', 'rosybrown4' => '8b6969', 'royalblue' => '4169e1', 'royalblue1' => '4876ff', 'royalblue2' => '436eee', 'royalblue3' => '3a5fcd', 'royalblue4' => '27408b', 'saddlebrown' => '8b4513', 'salmon' => 'fa8072', 'salmon1' => 'ff8c69', 'salmon2' => 'ee8262', 'salmon3' => 'cd7054', 'salmon4' => '8b4c39', 'sandybrown' => 'f4a460', 'seagreen1' => '54ff9f', 'seagreen2' => '4eee94', 'seagreen3' => '43cd80', 'seagreen4' => '2e8b57', 'seashell1' => 'fff5ee', 'seashell2' => 'eee5de', 'seashell3' => 'cdc5bf', 'seashell4' => '8b8682', 'sienna' => 'a0522d', 'sienna1' => 'ff8247', 'sienna2' => 'ee7942', 'sienna3' => 'cd6839', 'sienna4' => '8b4726', 'silver' => 'c0c0c0', 'skyblue' => '87ceeb', 'skyblue1' => '87ceff', 'skyblue2' => '7ec0ee', 'skyblue3' => '6ca6cd', 'skyblue4' => '4a708b', 'slateblue' => '6a5acd', 'slateblue1' => '836fff', 'slateblue2' => '7a67ee', 'slateblue3' => '6959cd', 'slateblue4' => '473c8b', 'slategray' => '708090', 'slategray1' => 'c6e2ff', 'slategray2' => 'b9d3ee', 'slategray3' => '9fb6cd', 'slategray4' => '6c7b8b', 'snow1' => 'fffafa', 'snow2' => 'eee9e9', 'snow3' => 'cdc9c9', 'snow4' => '8b8989', 'springgreen1' => '00ff7f', 'springgreen2' => '00ee76', 'springgreen3' => '00cd66', 'springgreen4' => '008b45', 'steelblue' => '4682b4', 'steelblue1' => '63b8ff', 'steelblue2' => '5cacee', 'steelblue3' => '4f94cd', 'steelblue4' => '36648b', 'tan' => 'd2b48c', 'tan1' => 'ffa54f', 'tan2' => 'ee9a49', 'tan3' => 'cd853f', 'tan4' => '8b5a2b', 'teal' => '008080', 'thistle' => 'd8bfd8', 'thistle1' => 'ffe1ff', 'thistle2' => 'eed2ee', 'thistle3' => 'cdb5cd', 'thistle4' => '8b7b8b', 'tomato1' => 'ff6347', 'tomato2' => 'ee5c42', 'tomato3' => 'cd4f39', 'tomato4' => '8b3626', 'turquoise' => '40e0d0', 'turquoise1' => '00f5ff', 'turquoise2' => '00e5ee', 'turquoise3' => '00c5cd', 'turquoise4' => '00868b', 'violet' => 'ee82ee', 'violetred' => 'd02090', 'violetred1' => 'ff3e96', 'violetred2' => 'ee3a8c', 'violetred3' => 'cd3278', 'violetred4' => '8b2252', 'wheat' => 'f5deb3', 'wheat1' => 'ffe7ba', 'wheat2' => 'eed8ae', 'wheat3' => 'cdba96', 'wheat4' => '8b7e66', 'white' => 'ffffff', 'whitesmoke' => 'f5f5f5', 'yellow' => 'ffff00', 'yellow1' => 'ffff00', 'yellow2' => 'eeee00', 'yellow3' => 'cdcd00', 'yellow4' => '8b8b00', 'yellowgreen' => '9acd32', ]; /** @var ?string */ private $face; /** @var ?string */ private $size; /** @var ?string */ private $color; /** @var bool */ private $bold = false; /** @var bool */ private $italic = false; /** @var bool */ private $underline = false; /** @var bool */ private $superscript = false; /** @var bool */ private $subscript = false; /** @var bool */ private $strikethrough = false; private const START_TAG_CALLBACKS = [ 'font' => 'startFontTag', 'b' => 'startBoldTag', 'strong' => 'startBoldTag', 'i' => 'startItalicTag', 'em' => 'startItalicTag', 'u' => 'startUnderlineTag', 'ins' => 'startUnderlineTag', 'del' => 'startStrikethruTag', 'sup' => 'startSuperscriptTag', 'sub' => 'startSubscriptTag', ]; private const END_TAG_CALLBACKS = [ 'font' => 'endFontTag', 'b' => 'endBoldTag', 'strong' => 'endBoldTag', 'i' => 'endItalicTag', 'em' => 'endItalicTag', 'u' => 'endUnderlineTag', 'ins' => 'endUnderlineTag', 'del' => 'endStrikethruTag', 'sup' => 'endSuperscriptTag', 'sub' => 'endSubscriptTag', 'br' => 'breakTag', 'p' => 'breakTag', 'h1' => 'breakTag', 'h2' => 'breakTag', 'h3' => 'breakTag', 'h4' => 'breakTag', 'h5' => 'breakTag', 'h6' => 'breakTag', ]; /** @var array */ private $stack = []; /** @var string */ private $stringData = ''; /** * @var RichText */ private $richTextObject; private function initialise(): void { $this->face = $this->size = $this->color = null; $this->bold = $this->italic = $this->underline = $this->superscript = $this->subscript = $this->strikethrough = false; $this->stack = []; $this->stringData = ''; } /** * Parse HTML formatting and return the resulting RichText. * * @param string $html * * @return RichText */ public function toRichTextObject($html) { $this->initialise(); // Create a new DOM object $dom = new DOMDocument(); // Load the HTML file into the DOM object // Note the use of error suppression, because typically this will be an html fragment, so not fully valid markup $prefix = ''; /** @scrutinizer ignore-unhandled */ @$dom->loadHTML($prefix . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); // Discard excess white space $dom->preserveWhiteSpace = false; $this->richTextObject = new RichText(); $this->parseElements($dom); // Clean any further spurious whitespace $this->cleanWhitespace(); return $this->richTextObject; } private function cleanWhitespace(): void { foreach ($this->richTextObject->getRichTextElements() as $key => $element) { $text = $element->getText(); // Trim any leading spaces on the first run if ($key == 0) { $text = ltrim($text); } // Trim any spaces immediately after a line break $text = (string) preg_replace('/\n */mu', "\n", $text); $element->setText($text); } } private function buildTextRun(): void { $text = $this->stringData; if (trim($text) === '') { return; } $richtextRun = $this->richTextObject->createTextRun($this->stringData); $font = $richtextRun->getFont(); if ($font !== null) { if ($this->face) { $font->setName($this->face); } if ($this->size) { $font->setSize($this->size); } if ($this->color) { $font->setColor(new Color('ff' . $this->color)); } if ($this->bold) { $font->setBold(true); } if ($this->italic) { $font->setItalic(true); } if ($this->underline) { $font->setUnderline(Font::UNDERLINE_SINGLE); } if ($this->superscript) { $font->setSuperscript(true); } if ($this->subscript) { $font->setSubscript(true); } if ($this->strikethrough) { $font->setStrikethrough(true); } } $this->stringData = ''; } private function rgbToColour(string $rgbValue): string { preg_match_all('/\d+/', $rgbValue, $values); foreach ($values[0] as &$value) { $value = str_pad(dechex($value), 2, '0', STR_PAD_LEFT); } return implode('', $values[0]); } public static function colourNameLookup(string $colorName): string { return self::COLOUR_MAP[$colorName] ?? ''; } protected function startFontTag(DOMElement $tag): void { $attrs = $tag->attributes; if ($attrs !== null) { foreach ($attrs as $attribute) { $attributeName = strtolower($attribute->name); $attributeValue = $attribute->value; if ($attributeName == 'color') { if (preg_match('/rgb\s*\(/', $attributeValue)) { $this->$attributeName = $this->rgbToColour($attributeValue); } elseif (strpos(trim($attributeValue), '#') === 0) { $this->$attributeName = ltrim($attributeValue, '#'); } else { $this->$attributeName = static::colourNameLookup($attributeValue); } } else { $this->$attributeName = $attributeValue; } } } } protected function endFontTag(): void { $this->face = $this->size = $this->color = null; } protected function startBoldTag(): void { $this->bold = true; } protected function endBoldTag(): void { $this->bold = false; } protected function startItalicTag(): void { $this->italic = true; } protected function endItalicTag(): void { $this->italic = false; } protected function startUnderlineTag(): void { $this->underline = true; } protected function endUnderlineTag(): void { $this->underline = false; } protected function startSubscriptTag(): void { $this->subscript = true; } protected function endSubscriptTag(): void { $this->subscript = false; } protected function startSuperscriptTag(): void { $this->superscript = true; } protected function endSuperscriptTag(): void { $this->superscript = false; } protected function startStrikethruTag(): void { $this->strikethrough = true; } protected function endStrikethruTag(): void { $this->strikethrough = false; } protected function breakTag(): void { $this->stringData .= "\n"; } private function parseTextNode(DOMText $textNode): void { $domText = (string) preg_replace( '/\s+/u', ' ', str_replace(["\r", "\n"], ' ', $textNode->nodeValue ?? '') ); $this->stringData .= $domText; $this->buildTextRun(); } /** * @param string $callbackTag */ private function handleCallback(DOMElement $element, $callbackTag, array $callbacks): void { if (isset($callbacks[$callbackTag])) { $elementHandler = $callbacks[$callbackTag]; if (method_exists($this, $elementHandler)) { /** @var callable */ $callable = [$this, $elementHandler]; call_user_func($callable, $element); } } } private function parseElementNode(DOMElement $element): void { $callbackTag = strtolower($element->nodeName); $this->stack[] = $callbackTag; $this->handleCallback($element, $callbackTag, self::START_TAG_CALLBACKS); $this->parseElements($element); array_pop($this->stack); $this->handleCallback($element, $callbackTag, self::END_TAG_CALLBACKS); } private function parseElements(DOMNode $element): void { foreach ($element->childNodes as $child) { if ($child instanceof DOMText) { $this->parseTextNode($child); } elseif ($child instanceof DOMElement) { $this->parseElementNode($child); } } } } phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php000064400000002013152427537250016606 0ustar00spreadsheet = $spreadsheet; } public function load(SimpleXMLElement $xml, array $namespacesMeta): void { $docProps = $this->spreadsheet->getProperties(); $officeProperty = $xml->children($namespacesMeta['office']); foreach ($officeProperty as $officePropertyData) { if (isset($namespacesMeta['dc'])) { /** @scrutinizer ignore-call */ $officePropertiesDC = $officePropertyData->children($namespacesMeta['dc']); $this->setCoreProperties($docProps, $officePropertiesDC); } $officePropertyMeta = null; if (isset($namespacesMeta['dc'])) { /** @scrutinizer ignore-call */ $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); } $officePropertyMeta = $officePropertyMeta ?? []; foreach ($officePropertyMeta as $propertyName => $propertyValue) { $this->setMetaProperties($namespacesMeta, $propertyValue, $propertyName, $docProps); } } } private function setCoreProperties(DocumentProperties $docProps, SimpleXMLElement $officePropertyDC): void { foreach ($officePropertyDC as $propertyName => $propertyValue) { $propertyValue = (string) $propertyValue; switch ($propertyName) { case 'title': $docProps->setTitle($propertyValue); break; case 'subject': $docProps->setSubject($propertyValue); break; case 'creator': $docProps->setCreator($propertyValue); $docProps->setLastModifiedBy($propertyValue); break; case 'date': $docProps->setModified($propertyValue); break; case 'description': $docProps->setDescription($propertyValue); break; } } } private function setMetaProperties( array $namespacesMeta, SimpleXMLElement $propertyValue, string $propertyName, DocumentProperties $docProps ): void { $propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']); $propertyValue = (string) $propertyValue; switch ($propertyName) { case 'initial-creator': $docProps->setCreator($propertyValue); break; case 'keyword': $docProps->setKeywords($propertyValue); break; case 'creation-date': $docProps->setCreated($propertyValue); break; case 'user-defined': $this->setUserDefinedProperty($propertyValueAttributes, $propertyValue, $docProps); break; } } /** * @param mixed $propertyValueAttributes * @param mixed $propertyValue */ private function setUserDefinedProperty($propertyValueAttributes, $propertyValue, DocumentProperties $docProps): void { $propertyValueName = ''; $propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING; foreach ($propertyValueAttributes as $key => $value) { if ($key == 'name') { $propertyValueName = (string) $value; } elseif ($key == 'value-type') { switch ($value) { case 'date': $propertyValue = DocumentProperties::convertProperty($propertyValue, 'date'); $propertyValueType = DocumentProperties::PROPERTY_TYPE_DATE; break; case 'boolean': $propertyValue = DocumentProperties::convertProperty($propertyValue, 'bool'); $propertyValueType = DocumentProperties::PROPERTY_TYPE_BOOLEAN; break; case 'float': $propertyValue = DocumentProperties::convertProperty($propertyValue, 'r4'); $propertyValueType = DocumentProperties::PROPERTY_TYPE_FLOAT; break; default: $propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING; } } } $docProps->setCustomProperty($propertyValueName, $propertyValue, $propertyValueType); } } phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php000064400000007120152427537250021424 0ustar00setDomNameSpaces($styleDom); $this->readPageSettingStyles($styleDom); $this->readStyleMasterLookup($styleDom); } private function setDomNameSpaces(DOMDocument $styleDom): void { $this->officeNs = $styleDom->lookupNamespaceUri('office'); $this->stylesNs = $styleDom->lookupNamespaceUri('style'); $this->stylesFo = $styleDom->lookupNamespaceUri('fo'); $this->tableNs = $styleDom->lookupNamespaceUri('table'); } private function readPageSettingStyles(DOMDocument $styleDom): void { $item0 = $styleDom->getElementsByTagNameNS($this->officeNs, 'automatic-styles')->item(0); $styles = ($item0 === null) ? [] : $item0->getElementsByTagNameNS($this->stylesNs, 'page-layout'); foreach ($styles as $styleSet) { $styleName = $styleSet->getAttributeNS($this->stylesNs, 'name'); $pageLayoutProperties = $styleSet->getElementsByTagNameNS($this->stylesNs, 'page-layout-properties')[0]; $styleOrientation = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-orientation'); $styleScale = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'scale-to'); $stylePrintOrder = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-page-order'); $centered = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'table-centering'); $marginLeft = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-left'); $marginRight = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-right'); $marginTop = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-top'); $marginBottom = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-bottom'); $header = $styleSet->getElementsByTagNameNS($this->stylesNs, 'header-style')[0]; $headerProperties = $header->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0]; $marginHeader = isset($headerProperties) ? $headerProperties->getAttributeNS($this->stylesFo, 'min-height') : null; $footer = $styleSet->getElementsByTagNameNS($this->stylesNs, 'footer-style')[0]; $footerProperties = $footer->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0]; $marginFooter = isset($footerProperties) ? $footerProperties->getAttributeNS($this->stylesFo, 'min-height') : null; $this->pageLayoutStyles[$styleName] = (object) [ 'orientation' => $styleOrientation ?: PageSetup::ORIENTATION_DEFAULT, 'scale' => $styleScale ?: 100, 'printOrder' => $stylePrintOrder, 'horizontalCentered' => $centered === 'horizontal' || $centered === 'both', 'verticalCentered' => $centered === 'vertical' || $centered === 'both', // margin size is already stored in inches, so no UOM conversion is required 'marginLeft' => (float) ($marginLeft ?? 0.7), 'marginRight' => (float) ($marginRight ?? 0.7), 'marginTop' => (float) ($marginTop ?? 0.3), 'marginBottom' => (float) ($marginBottom ?? 0.3), 'marginHeader' => (float) ($marginHeader ?? 0.45), 'marginFooter' => (float) ($marginFooter ?? 0.45), ]; } } private function readStyleMasterLookup(DOMDocument $styleDom): void { $item0 = $styleDom->getElementsByTagNameNS($this->officeNs, 'master-styles')->item(0); $styleMasterLookup = ($item0 === null) ? [] : $item0->getElementsByTagNameNS($this->stylesNs, 'master-page'); foreach ($styleMasterLookup as $styleMasterSet) { $styleMasterName = $styleMasterSet->getAttributeNS($this->stylesNs, 'name'); $pageLayoutName = $styleMasterSet->getAttributeNS($this->stylesNs, 'page-layout-name'); $this->masterPrintStylesCrossReference[$styleMasterName] = $pageLayoutName; } } public function readStyleCrossReferences(DOMDocument $contentDom): void { $item0 = $contentDom->getElementsByTagNameNS($this->officeNs, 'automatic-styles')->item(0); $styleXReferences = ($item0 === null) ? [] : $item0->getElementsByTagNameNS($this->stylesNs, 'style'); foreach ($styleXReferences as $styleXreferenceSet) { $styleXRefName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'name'); $stylePageLayoutName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'master-page-name'); $styleFamilyName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'family'); if (!empty($styleFamilyName) && $styleFamilyName === 'table') { $styleVisibility = 'true'; foreach ($styleXreferenceSet->getElementsByTagNameNS($this->stylesNs, 'table-properties') as $tableProperties) { $styleVisibility = $tableProperties->getAttributeNS($this->tableNs, 'display'); } $this->tableStylesCrossReference[$styleXRefName] = $styleVisibility; } if (!empty($stylePageLayoutName)) { $this->masterStylesCrossReference[$styleXRefName] = $stylePageLayoutName; } } } public function setVisibilityForWorksheet(Worksheet $worksheet, string $styleName): void { if (!array_key_exists($styleName, $this->tableStylesCrossReference)) { return; } $worksheet->setSheetState( $this->tableStylesCrossReference[$styleName] === 'false' ? Worksheet::SHEETSTATE_HIDDEN : Worksheet::SHEETSTATE_VISIBLE ); } public function setPrintSettingsForWorksheet(Worksheet $worksheet, string $styleName): void { if (!array_key_exists($styleName, $this->masterStylesCrossReference)) { return; } $masterStyleName = $this->masterStylesCrossReference[$styleName]; if (!array_key_exists($masterStyleName, $this->masterPrintStylesCrossReference)) { return; } $printSettingsIndex = $this->masterPrintStylesCrossReference[$masterStyleName]; if (!array_key_exists($printSettingsIndex, $this->pageLayoutStyles)) { return; } $printSettings = $this->pageLayoutStyles[$printSettingsIndex]; $worksheet->getPageSetup() ->setOrientation($printSettings->orientation ?? PageSetup::ORIENTATION_DEFAULT) ->setPageOrder($printSettings->printOrder === 'ltr' ? PageSetup::PAGEORDER_OVER_THEN_DOWN : PageSetup::PAGEORDER_DOWN_THEN_OVER) ->setScale((int) trim($printSettings->scale, '%')) ->setHorizontalCentered($printSettings->horizontalCentered) ->setVerticalCentered($printSettings->verticalCentered); $worksheet->getPageMargins() ->setLeft($printSettings->marginLeft) ->setRight($printSettings->marginRight) ->setTop($printSettings->marginTop) ->setBottom($printSettings->marginBottom) ->setHeader($printSettings->marginHeader) ->setFooter($printSettings->marginFooter); } } phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseLoader.php000064400000000772152427537250017754 0ustar00spreadsheet = $spreadsheet; $this->tableNs = $tableNs; } abstract public function read(DOMElement $workbookData): void; } phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php000064400000005353152427537250020275 0ustar00readDefinedRanges($workbookData); $this->readDefinedExpressions($workbookData); } /** * Read any Named Ranges that are defined in this spreadsheet. */ protected function readDefinedRanges(DOMElement $workbookData): void { $namedRanges = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-range'); foreach ($namedRanges as $definedNameElement) { $definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name'); $baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address'); $range = $definedNameElement->getAttributeNS($this->tableNs, 'cell-range-address'); $baseAddress = FormulaTranslator::convertToExcelAddressValue($baseAddress); $range = FormulaTranslator::convertToExcelAddressValue($range); $this->addDefinedName($baseAddress, $definedName, $range); } } /** * Read any Named Formulae that are defined in this spreadsheet. */ protected function readDefinedExpressions(DOMElement $workbookData): void { $namedExpressions = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-expression'); foreach ($namedExpressions as $definedNameElement) { $definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name'); $baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address'); $expression = $definedNameElement->getAttributeNS($this->tableNs, 'expression'); $baseAddress = FormulaTranslator::convertToExcelAddressValue($baseAddress); $expression = substr($expression, strpos($expression, ':=') + 1); $expression = FormulaTranslator::convertToExcelFormulaValue($expression); $this->addDefinedName($baseAddress, $definedName, $expression); } } /** * Assess scope and store the Defined Name. */ private function addDefinedName(string $baseAddress, string $definedName, string $value): void { [$sheetReference] = Worksheet::extractSheetTitle($baseAddress, true); $worksheet = $this->spreadsheet->getSheetByName($sheetReference); // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet if ($worksheet !== null) { $this->spreadsheet->addDefinedName(DefinedName::createInstance((string) $definedName, $worksheet, $value)); } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php000064400000002474152427537250020032 0ustar00readAutoFilters($workbookData); } protected function readAutoFilters(DOMElement $workbookData): void { $databases = $workbookData->getElementsByTagNameNS($this->tableNs, 'database-ranges'); foreach ($databases as $autofilters) { foreach ($autofilters->childNodes as $autofilter) { $autofilterRange = $this->getAttributeValue($autofilter, 'target-range-address'); if ($autofilterRange !== null) { $baseAddress = FormulaTranslator::convertToExcelAddressValue($autofilterRange); $this->spreadsheet->getActiveSheet()->setAutoFilter($baseAddress); } } } } protected function getAttributeValue(?DOMNode $node, string $attributeName): ?string { if ($node !== null && $node->attributes !== null) { $attribute = $node->attributes->getNamedItemNS( $this->tableNs, $attributeName ); if ($attribute !== null) { return $attribute->nodeValue; } } return null; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php000064400000010543152427537250020315 0ustar00securityScanner = $securityScanner; $this->docProps = $docProps; } /** * @param mixed $obj */ private static function nullOrSimple($obj): ?SimpleXMLElement { return ($obj instanceof SimpleXMLElement) ? $obj : null; } private function extractPropertyData(string $propertyData): ?SimpleXMLElement { // okay to omit namespace because everything will be processed by xpath $obj = simplexml_load_string( $this->securityScanner->scan($propertyData) ); return self::nullOrSimple($obj); } public function readCoreProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); if (is_object($xmlCore)) { $xmlCore->registerXPathNamespace('dc', Namespaces::DC_ELEMENTS); $xmlCore->registerXPathNamespace('dcterms', Namespaces::DC_TERMS); $xmlCore->registerXPathNamespace('cp', Namespaces::CORE_PROPERTIES2); $this->docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator'))); $this->docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy'))); $this->docProps->setCreated((string) self::getArrayItem($xmlCore->xpath('dcterms:created'))); //! respect xsi:type $this->docProps->setModified((string) self::getArrayItem($xmlCore->xpath('dcterms:modified'))); //! respect xsi:type $this->docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title'))); $this->docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description'))); $this->docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject'))); $this->docProps->setKeywords((string) self::getArrayItem($xmlCore->xpath('cp:keywords'))); $this->docProps->setCategory((string) self::getArrayItem($xmlCore->xpath('cp:category'))); } } public function readExtendedProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); if (is_object($xmlCore)) { if (isset($xmlCore->Company)) { $this->docProps->setCompany((string) $xmlCore->Company); } if (isset($xmlCore->Manager)) { $this->docProps->setManager((string) $xmlCore->Manager); } if (isset($xmlCore->HyperlinkBase)) { $this->docProps->setHyperlinkBase((string) $xmlCore->HyperlinkBase); } } } public function readCustomProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); if (is_object($xmlCore)) { foreach ($xmlCore as $xmlProperty) { /** @var SimpleXMLElement $xmlProperty */ $cellDataOfficeAttributes = $xmlProperty->attributes(); if (isset($cellDataOfficeAttributes['name'])) { $propertyName = (string) $cellDataOfficeAttributes['name']; $cellDataOfficeChildren = $xmlProperty->children('http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); $attributeType = $cellDataOfficeChildren->getName(); $attributeValue = (string) $cellDataOfficeChildren->{$attributeType}; $attributeValue = DocumentProperties::convertProperty($attributeValue, $attributeType); $attributeType = DocumentProperties::convertPropertyType($attributeType); $this->docProps->setCustomProperty($propertyName, $attributeValue, $attributeType); } } } } /** * @param null|array|false $array * @param mixed $key */ private static function getArrayItem($array, $key = 0): ?SimpleXMLElement { return is_array($array) ? ($array[$key] ?? null) : null; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php000064400000011331152427537250020243 0ustar00sheetViewXml = $sheetViewXml; $this->sheetViewAttributes = Xlsx::testSimpleXml($sheetViewXml->attributes()); $this->worksheet = $workSheet; } public function load(): void { $this->topLeft(); $this->zoomScale(); $this->view(); $this->gridLines(); $this->headers(); $this->direction(); $this->showZeros(); if (isset($this->sheetViewXml->pane)) { $this->pane(); } if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection->attributes()->sqref)) { $this->selection(); } } private function zoomScale(): void { if (isset($this->sheetViewAttributes->zoomScale)) { $zoomScale = (int) ($this->sheetViewAttributes->zoomScale); if ($zoomScale <= 0) { // setZoomScale will throw an Exception if the scale is less than or equals 0 // that is OK when manually creating documents, but we should be able to read all documents $zoomScale = 100; } $this->worksheet->getSheetView()->setZoomScale($zoomScale); } if (isset($this->sheetViewAttributes->zoomScaleNormal)) { $zoomScaleNormal = (int) ($this->sheetViewAttributes->zoomScaleNormal); if ($zoomScaleNormal <= 0) { // setZoomScaleNormal will throw an Exception if the scale is less than or equals 0 // that is OK when manually creating documents, but we should be able to read all documents $zoomScaleNormal = 100; } $this->worksheet->getSheetView()->setZoomScaleNormal($zoomScaleNormal); } } private function view(): void { if (isset($this->sheetViewAttributes->view)) { $this->worksheet->getSheetView()->setView((string) $this->sheetViewAttributes->view); } } private function topLeft(): void { if (isset($this->sheetViewAttributes->topLeftCell)) { $this->worksheet->setTopLeftCell($this->sheetViewAttributes->topLeftCell); } } private function gridLines(): void { if (isset($this->sheetViewAttributes->showGridLines)) { $this->worksheet->setShowGridLines( self::boolean((string) $this->sheetViewAttributes->showGridLines) ); } } private function headers(): void { if (isset($this->sheetViewAttributes->showRowColHeaders)) { $this->worksheet->setShowRowColHeaders( self::boolean((string) $this->sheetViewAttributes->showRowColHeaders) ); } } private function direction(): void { if (isset($this->sheetViewAttributes->rightToLeft)) { $this->worksheet->setRightToLeft( self::boolean((string) $this->sheetViewAttributes->rightToLeft) ); } } private function showZeros(): void { if (isset($this->sheetViewAttributes->showZeros)) { $this->worksheet->getSheetView()->setShowZeros( self::boolean((string) $this->sheetViewAttributes->showZeros) ); } } private function pane(): void { $xSplit = 0; $ySplit = 0; $topLeftCell = null; $paneAttributes = $this->sheetViewXml->pane->attributes(); if (isset($paneAttributes->xSplit)) { $xSplit = (int) ($paneAttributes->xSplit); } if (isset($paneAttributes->ySplit)) { $ySplit = (int) ($paneAttributes->ySplit); } if (isset($paneAttributes->topLeftCell)) { $topLeftCell = (string) $paneAttributes->topLeftCell; } $this->worksheet->freezePane( Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1), $topLeftCell ); } private function selection(): void { $attributes = $this->sheetViewXml->selection->attributes(); if ($attributes !== null) { $sqref = (string) $attributes->sqref; $sqref = explode(' ', $sqref); $sqref = $sqref[0]; $this->worksheet->setSelectedCells($sqref); } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php000064400000012255152427537250020242 0ustar00worksheet = $workSheet; $this->worksheetXml = $worksheetXml; } /** * Set Worksheet column attributes by attributes array passed. * * @param string $columnAddress A, B, ... DX, ... * @param array $columnAttributes array of attributes (indexes are attribute name, values are value) * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ? */ private function setColumnAttributes($columnAddress, array $columnAttributes): void { if (isset($columnAttributes['xfIndex'])) { $this->worksheet->getColumnDimension($columnAddress)->setXfIndex($columnAttributes['xfIndex']); } if (isset($columnAttributes['visible'])) { $this->worksheet->getColumnDimension($columnAddress)->setVisible($columnAttributes['visible']); } if (isset($columnAttributes['collapsed'])) { $this->worksheet->getColumnDimension($columnAddress)->setCollapsed($columnAttributes['collapsed']); } if (isset($columnAttributes['outlineLevel'])) { $this->worksheet->getColumnDimension($columnAddress)->setOutlineLevel($columnAttributes['outlineLevel']); } if (isset($columnAttributes['width'])) { $this->worksheet->getColumnDimension($columnAddress)->setWidth($columnAttributes['width']); } } /** * Set Worksheet row attributes by attributes array passed. * * @param int $rowNumber 1, 2, 3, ... 99, ... * @param array $rowAttributes array of attributes (indexes are attribute name, values are value) * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ? */ private function setRowAttributes($rowNumber, array $rowAttributes): void { if (isset($rowAttributes['xfIndex'])) { $this->worksheet->getRowDimension($rowNumber)->setXfIndex($rowAttributes['xfIndex']); } if (isset($rowAttributes['visible'])) { $this->worksheet->getRowDimension($rowNumber)->setVisible($rowAttributes['visible']); } if (isset($rowAttributes['collapsed'])) { $this->worksheet->getRowDimension($rowNumber)->setCollapsed($rowAttributes['collapsed']); } if (isset($rowAttributes['outlineLevel'])) { $this->worksheet->getRowDimension($rowNumber)->setOutlineLevel($rowAttributes['outlineLevel']); } if (isset($rowAttributes['rowHeight'])) { $this->worksheet->getRowDimension($rowNumber)->setRowHeight($rowAttributes['rowHeight']); } } public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false): void { if ($this->worksheetXml === null) { return; } $columnsAttributes = []; $rowsAttributes = []; if (isset($this->worksheetXml->cols)) { $columnsAttributes = $this->readColumnAttributes($this->worksheetXml->cols, $readDataOnly); } if ($this->worksheetXml->sheetData && $this->worksheetXml->sheetData->row) { $rowsAttributes = $this->readRowAttributes($this->worksheetXml->sheetData->row, $readDataOnly); } if ($readFilter !== null && get_class($readFilter) === DefaultReadFilter::class) { $readFilter = null; } // set columns/rows attributes $columnsAttributesAreSet = []; foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) { if ( $readFilter === null || !$this->isFilteredColumn($readFilter, $columnCoordinate, $rowsAttributes) ) { if (!isset($columnsAttributesAreSet[$columnCoordinate])) { $this->setColumnAttributes($columnCoordinate, $columnAttributes); $columnsAttributesAreSet[$columnCoordinate] = true; } } } $rowsAttributesAreSet = []; foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) { if ( $readFilter === null || !$this->isFilteredRow($readFilter, $rowCoordinate, $columnsAttributes) ) { if (!isset($rowsAttributesAreSet[$rowCoordinate])) { $this->setRowAttributes($rowCoordinate, $rowAttributes); $rowsAttributesAreSet[$rowCoordinate] = true; } } } } private function isFilteredColumn(IReadFilter $readFilter, string $columnCoordinate, array $rowsAttributes): bool { foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) { if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) { return true; } } return false; } private function readColumnAttributes(SimpleXMLElement $worksheetCols, bool $readDataOnly): array { $columnAttributes = []; foreach ($worksheetCols->col as $columnx) { /** @scrutinizer ignore-call */ $column = $columnx->attributes(); if ($column !== null) { $startColumn = Coordinate::stringFromColumnIndex((int) $column['min']); $endColumn = Coordinate::stringFromColumnIndex((int) $column['max']); ++$endColumn; for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) { $columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly); if ((int) ($column['max']) == 16384) { break; } } } } return $columnAttributes; } private function readColumnRangeAttributes(?SimpleXMLElement $column, bool $readDataOnly): array { $columnAttributes = []; if ($column !== null) { if (isset($column['style']) && !$readDataOnly) { $columnAttributes['xfIndex'] = (int) $column['style']; } if (isset($column['hidden']) && self::boolean($column['hidden'])) { $columnAttributes['visible'] = false; } if (isset($column['collapsed']) && self::boolean($column['collapsed'])) { $columnAttributes['collapsed'] = true; } if (isset($column['outlineLevel']) && ((int) $column['outlineLevel']) > 0) { $columnAttributes['outlineLevel'] = (int) $column['outlineLevel']; } if (isset($column['width'])) { $columnAttributes['width'] = (float) $column['width']; } } return $columnAttributes; } private function isFilteredRow(IReadFilter $readFilter, int $rowCoordinate, array $columnsAttributes): bool { foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) { if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) { return true; } } return false; } private function readRowAttributes(SimpleXMLElement $worksheetRow, bool $readDataOnly): array { $rowAttributes = []; foreach ($worksheetRow as $rowx) { /** @scrutinizer ignore-call */ $row = $rowx->attributes(); if ($row !== null) { if (isset($row['ht']) && !$readDataOnly) { $rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht']; } if (isset($row['hidden']) && self::boolean($row['hidden'])) { $rowAttributes[(int) $row['r']]['visible'] = false; } if (isset($row['collapsed']) && self::boolean($row['collapsed'])) { $rowAttributes[(int) $row['r']]['collapsed'] = true; } if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) { $rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel']; } if (isset($row['s']) && !$readDataOnly) { $rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s']; } } } return $rowAttributes; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php000064400000015742152427537250020064 0ustar00worksheet = $workSheet; $this->worksheetXml = $worksheetXml; } public function load(array $unparsedLoadedData): array { $worksheetXml = $this->worksheetXml; if ($worksheetXml === null) { return $unparsedLoadedData; } $this->margins($worksheetXml, $this->worksheet); $unparsedLoadedData = $this->pageSetup($worksheetXml, $this->worksheet, $unparsedLoadedData); $this->headerFooter($worksheetXml, $this->worksheet); $this->pageBreaks($worksheetXml, $this->worksheet); return $unparsedLoadedData; } private function margins(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void { if ($xmlSheet->pageMargins) { $docPageMargins = $worksheet->getPageMargins(); $docPageMargins->setLeft((float) ($xmlSheet->pageMargins['left'])); $docPageMargins->setRight((float) ($xmlSheet->pageMargins['right'])); $docPageMargins->setTop((float) ($xmlSheet->pageMargins['top'])); $docPageMargins->setBottom((float) ($xmlSheet->pageMargins['bottom'])); $docPageMargins->setHeader((float) ($xmlSheet->pageMargins['header'])); $docPageMargins->setFooter((float) ($xmlSheet->pageMargins['footer'])); } } private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData): array { if ($xmlSheet->pageSetup) { $docPageSetup = $worksheet->getPageSetup(); if (isset($xmlSheet->pageSetup['orientation'])) { $docPageSetup->setOrientation((string) $xmlSheet->pageSetup['orientation']); } if (isset($xmlSheet->pageSetup['paperSize'])) { $docPageSetup->setPaperSize((int) ($xmlSheet->pageSetup['paperSize'])); } if (isset($xmlSheet->pageSetup['scale'])) { $docPageSetup->setScale((int) ($xmlSheet->pageSetup['scale']), false); } if (isset($xmlSheet->pageSetup['fitToHeight']) && (int) ($xmlSheet->pageSetup['fitToHeight']) >= 0) { $docPageSetup->setFitToHeight((int) ($xmlSheet->pageSetup['fitToHeight']), false); } if (isset($xmlSheet->pageSetup['fitToWidth']) && (int) ($xmlSheet->pageSetup['fitToWidth']) >= 0) { $docPageSetup->setFitToWidth((int) ($xmlSheet->pageSetup['fitToWidth']), false); } if ( isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) && self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber']) ) { $docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber'])); } if (isset($xmlSheet->pageSetup['pageOrder'])) { $docPageSetup->setPageOrder((string) $xmlSheet->pageSetup['pageOrder']); } $relAttributes = $xmlSheet->pageSetup->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT); if (isset($relAttributes['id'])) { $relid = (string) $relAttributes['id']; if (substr($relid, -2) !== 'ps') { $relid .= 'ps'; } $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = $relid; } } return $unparsedLoadedData; } private function headerFooter(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void { if ($xmlSheet->headerFooter) { $docHeaderFooter = $worksheet->getHeaderFooter(); if ( isset($xmlSheet->headerFooter['differentOddEven']) && self::boolean((string) $xmlSheet->headerFooter['differentOddEven']) ) { $docHeaderFooter->setDifferentOddEven(true); } else { $docHeaderFooter->setDifferentOddEven(false); } if ( isset($xmlSheet->headerFooter['differentFirst']) && self::boolean((string) $xmlSheet->headerFooter['differentFirst']) ) { $docHeaderFooter->setDifferentFirst(true); } else { $docHeaderFooter->setDifferentFirst(false); } if ( isset($xmlSheet->headerFooter['scaleWithDoc']) && !self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc']) ) { $docHeaderFooter->setScaleWithDocument(false); } else { $docHeaderFooter->setScaleWithDocument(true); } if ( isset($xmlSheet->headerFooter['alignWithMargins']) && !self::boolean((string) $xmlSheet->headerFooter['alignWithMargins']) ) { $docHeaderFooter->setAlignWithMargins(false); } else { $docHeaderFooter->setAlignWithMargins(true); } $docHeaderFooter->setOddHeader((string) $xmlSheet->headerFooter->oddHeader); $docHeaderFooter->setOddFooter((string) $xmlSheet->headerFooter->oddFooter); $docHeaderFooter->setEvenHeader((string) $xmlSheet->headerFooter->evenHeader); $docHeaderFooter->setEvenFooter((string) $xmlSheet->headerFooter->evenFooter); $docHeaderFooter->setFirstHeader((string) $xmlSheet->headerFooter->firstHeader); $docHeaderFooter->setFirstFooter((string) $xmlSheet->headerFooter->firstFooter); } } private function pageBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void { if ($xmlSheet->rowBreaks && $xmlSheet->rowBreaks->brk) { $this->rowBreaks($xmlSheet, $worksheet); } if ($xmlSheet->colBreaks && $xmlSheet->colBreaks->brk) { $this->columnBreaks($xmlSheet, $worksheet); } } private function rowBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void { foreach ($xmlSheet->rowBreaks->brk as $brk) { $rowBreakMax = isset($brk['max']) ? ((int) $brk['max']) : -1; if ($brk['man']) { $worksheet->setBreak("A{$brk['id']}", Worksheet::BREAK_ROW, $rowBreakMax); } } } private function columnBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void { foreach ($xmlSheet->colBreaks->brk as $brk) { if ($brk['man']) { $worksheet->setBreak( Coordinate::stringFromColumnIndex(((int) $brk['id']) + 1) . '1', Worksheet::BREAK_COLUMN ); } } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php000064400000025721152427537250021634 0ustar00worksheet = $workSheet; $this->worksheetXml = $worksheetXml; $this->dxfs = $dxfs; } public function load(): void { $selectedCells = $this->worksheet->getSelectedCells(); $this->setConditionalStyles( $this->worksheet, $this->readConditionalStyles($this->worksheetXml), $this->worksheetXml->extLst ); $this->worksheet->setSelectedCells($selectedCells); } public function loadFromExt(StyleReader $styleReader): void { $selectedCells = $this->worksheet->getSelectedCells(); $this->ns = $this->worksheetXml->getNamespaces(true); $this->setConditionalsFromExt( $this->readConditionalsFromExt($this->worksheetXml->extLst, $styleReader) ); $this->worksheet->setSelectedCells($selectedCells); } private function setConditionalsFromExt(array $conditionals): void { foreach ($conditionals as $conditionalRange => $cfRules) { ksort($cfRules); // Priority is used as the key for sorting; but may not start at 0, // so we use array_values to reset the index after sorting. $this->worksheet->getStyle($conditionalRange) ->setConditionalStyles(array_values($cfRules)); } } private function readConditionalsFromExt(SimpleXMLElement $extLst, StyleReader $styleReader): array { $conditionals = []; if (isset($extLst->ext['uri']) && (string) $extLst->ext['uri'] === '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') { $conditionalFormattingRuleXml = $extLst->ext->children($this->ns['x14']); if (!$conditionalFormattingRuleXml->conditionalFormattings) { return []; } foreach ($conditionalFormattingRuleXml->children($this->ns['x14']) as $extFormattingXml) { $extFormattingRangeXml = $extFormattingXml->children($this->ns['xm']); if (!$extFormattingRangeXml->sqref) { continue; } $sqref = (string) $extFormattingRangeXml->sqref; $extCfRuleXml = $extFormattingXml->cfRule; $attributes = $extCfRuleXml->attributes(); if (!$attributes) { continue; } $conditionType = (string) $attributes->type; if ( !Conditional::isValidConditionType($conditionType) || $conditionType === Conditional::CONDITION_DATABAR ) { continue; } $priority = (int) $attributes->priority; $conditional = $this->readConditionalRuleFromExt($extCfRuleXml, $attributes); $cfStyle = $this->readStyleFromExt($extCfRuleXml, $styleReader); $conditional->setStyle($cfStyle); $conditionals[$sqref][$priority] = $conditional; } } return $conditionals; } private function readConditionalRuleFromExt(SimpleXMLElement $cfRuleXml, SimpleXMLElement $attributes): Conditional { $conditionType = (string) $attributes->type; $operatorType = (string) $attributes->operator; $operands = []; foreach ($cfRuleXml->children($this->ns['xm']) as $cfRuleOperandsXml) { $operands[] = (string) $cfRuleOperandsXml; } $conditional = new Conditional(); $conditional->setConditionType($conditionType); $conditional->setOperatorType($operatorType); if ( $conditionType === Conditional::CONDITION_CONTAINSTEXT || $conditionType === Conditional::CONDITION_NOTCONTAINSTEXT || $conditionType === Conditional::CONDITION_BEGINSWITH || $conditionType === Conditional::CONDITION_ENDSWITH || $conditionType === Conditional::CONDITION_TIMEPERIOD ) { $conditional->setText(array_pop($operands) ?? ''); } $conditional->setConditions($operands); return $conditional; } private function readStyleFromExt(SimpleXMLElement $extCfRuleXml, StyleReader $styleReader): Style { $cfStyle = new Style(false, true); if ($extCfRuleXml->dxf) { $styleXML = $extCfRuleXml->dxf->children(); if ($styleXML->borders) { $styleReader->readBorderStyle($cfStyle->getBorders(), $styleXML->borders); } if ($styleXML->fill) { $styleReader->readFillStyle($cfStyle->getFill(), $styleXML->fill); } } return $cfStyle; } private function readConditionalStyles(SimpleXMLElement $xmlSheet): array { $conditionals = []; foreach ($xmlSheet->conditionalFormatting as $conditional) { foreach ($conditional->cfRule as $cfRule) { if (Conditional::isValidConditionType((string) $cfRule['type']) && (!isset($cfRule['dxfId']) || isset($this->dxfs[(int) ($cfRule['dxfId'])]))) { $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; } elseif ((string) $cfRule['type'] == Conditional::CONDITION_DATABAR) { $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; } } } return $conditionals; } private function setConditionalStyles(Worksheet $worksheet, array $conditionals, SimpleXMLElement $xmlExtLst): void { foreach ($conditionals as $cellRangeReference => $cfRules) { ksort($cfRules); $conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst); // Extract all cell references in $cellRangeReference $cellBlocks = explode(' ', str_replace('$', '', strtoupper($cellRangeReference))); foreach ($cellBlocks as $cellBlock) { $worksheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles); } } } private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array { $conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst); $conditionalStyles = []; foreach ($cfRules as $cfRule) { $objConditional = new Conditional(); $objConditional->setConditionType((string) $cfRule['type']); $objConditional->setOperatorType((string) $cfRule['operator']); $objConditional->setNoFormatSet(!isset($cfRule['dxfId'])); if ((string) $cfRule['text'] != '') { $objConditional->setText((string) $cfRule['text']); } elseif ((string) $cfRule['timePeriod'] != '') { $objConditional->setText((string) $cfRule['timePeriod']); } if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) { $objConditional->setStopIfTrue(true); } if (count($cfRule->formula) >= 1) { foreach ($cfRule->formula as $formulax) { $formula = (string) $formulax; if ($formula === 'TRUE') { $objConditional->addCondition(true); } elseif ($formula === 'FALSE') { $objConditional->addCondition(false); } else { $objConditional->addCondition($formula); } } } else { $objConditional->addCondition(''); } if (isset($cfRule->dataBar)) { $objConditional->setDataBar( $this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions) // @phpstan-ignore-line ); } elseif (isset($cfRule['dxfId'])) { $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]); } $conditionalStyles[] = $objConditional; } return $conditionalStyles; } /** * @param SimpleXMLElement|stdClass $cfRule */ private function readDataBarOfConditionalRule($cfRule, array $conditionalFormattingRuleExtensions): ConditionalDataBar { $dataBar = new ConditionalDataBar(); //dataBar attribute if (isset($cfRule->dataBar['showValue'])) { $dataBar->setShowValue((bool) $cfRule->dataBar['showValue']); } //dataBar children //conditionalFormatValueObjects $cfvoXml = $cfRule->dataBar->cfvo; $cfvoIndex = 0; foreach ((count($cfvoXml) > 1 ? $cfvoXml : [$cfvoXml]) as $cfvo) { if ($cfvoIndex === 0) { $dataBar->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val'])); } if ($cfvoIndex === 1) { $dataBar->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val'])); } ++$cfvoIndex; } //color if (isset($cfRule->dataBar->color)) { $dataBar->setColor((string) $cfRule->dataBar->color['rgb']); } //extLst $this->readDataBarExtLstOfConditionalRule($dataBar, $cfRule, $conditionalFormattingRuleExtensions); return $dataBar; } /** * @param SimpleXMLElement|stdClass $cfRule */ private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, $cfRule, array $conditionalFormattingRuleExtensions): void { if (isset($cfRule->extLst)) { $ns = $cfRule->extLst->getNamespaces(true); foreach ((count($cfRule->extLst) > 0 ? $cfRule->extLst->ext : [$cfRule->extLst->ext]) as $ext) { $extId = (string) $ext->children($ns['x14'])->id; if (isset($conditionalFormattingRuleExtensions[$extId]) && (string) $ext['uri'] === '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}') { $dataBar->setConditionalFormattingRuleExt($conditionalFormattingRuleExtensions[$extId]); } } } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php000064400000037711152427537250017452 0ustar00namespace = $namespace; } public function setWorkbookPalette(array $palette): void { $this->workbookPalette = $palette; } /** * Cast SimpleXMLElement to bool to overcome Scrutinizer problem. * * @param mixed $value */ private static function castBool($value): bool { return (bool) $value; } private function getStyleAttributes(SimpleXMLElement $value): SimpleXMLElement { $attr = null; if (self::castBool($value)) { $attr = $value->attributes(''); if ($attr === null || count($attr) === 0) { $attr = $value->attributes($this->namespace); } } return Xlsx::testSimpleXml($attr); } public function setStyleXml(SimpleXmlElement $styleXml): void { $this->styleXml = $styleXml; } public function setTheme(Theme $theme): void { $this->theme = $theme; } public function setStyleBaseData(?Theme $theme = null, array $styles = [], array $cellStyles = []): void { $this->theme = $theme; $this->styles = $styles; $this->cellStyles = $cellStyles; } public function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void { if (isset($fontStyleXml->name)) { $attr = $this->getStyleAttributes($fontStyleXml->name); if (isset($attr['val'])) { $fontStyle->setName((string) $attr['val']); } } if (isset($fontStyleXml->sz)) { $attr = $this->getStyleAttributes($fontStyleXml->sz); if (isset($attr['val'])) { $fontStyle->setSize((float) $attr['val']); } } if (isset($fontStyleXml->b)) { $attr = $this->getStyleAttributes($fontStyleXml->b); $fontStyle->setBold(!isset($attr['val']) || self::boolean((string) $attr['val'])); } if (isset($fontStyleXml->i)) { $attr = $this->getStyleAttributes($fontStyleXml->i); $fontStyle->setItalic(!isset($attr['val']) || self::boolean((string) $attr['val'])); } if (isset($fontStyleXml->strike)) { $attr = $this->getStyleAttributes($fontStyleXml->strike); $fontStyle->setStrikethrough(!isset($attr['val']) || self::boolean((string) $attr['val'])); } $fontStyle->getColor()->setARGB($this->readColor($fontStyleXml->color)); if (isset($fontStyleXml->u)) { $attr = $this->getStyleAttributes($fontStyleXml->u); if (!isset($attr['val'])) { $fontStyle->setUnderline(Font::UNDERLINE_SINGLE); } else { $fontStyle->setUnderline((string) $attr['val']); } } if (isset($fontStyleXml->vertAlign)) { $attr = $this->getStyleAttributes($fontStyleXml->vertAlign); if (isset($attr['val'])) { $verticalAlign = strtolower((string) $attr['val']); if ($verticalAlign === 'superscript') { $fontStyle->setSuperscript(true); } elseif ($verticalAlign === 'subscript') { $fontStyle->setSubscript(true); } } } if (isset($fontStyleXml->scheme)) { $attr = $this->getStyleAttributes($fontStyleXml->scheme); $fontStyle->setScheme((string) $attr['val']); } } private function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void { if ((string) $numfmtStyleXml['formatCode'] !== '') { $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmtStyleXml['formatCode'])); return; } $numfmt = $this->getStyleAttributes($numfmtStyleXml); if (isset($numfmt['formatCode'])) { $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmt['formatCode'])); } } public function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void { if ($fillStyleXml->gradientFill) { /** @var SimpleXMLElement $gradientFill */ $gradientFill = $fillStyleXml->gradientFill[0]; $attr = $this->getStyleAttributes($gradientFill); if (!empty($attr['type'])) { $fillStyle->setFillType((string) $attr['type']); } $fillStyle->setRotation((float) ($attr['degree'])); $gradientFill->registerXPathNamespace('sml', Namespaces::MAIN); $fillStyle->getStartColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); $fillStyle->getEndColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); } elseif ($fillStyleXml->patternFill) { $defaultFillStyle = Fill::FILL_NONE; if ($fillStyleXml->patternFill->fgColor) { $fillStyle->getStartColor()->setARGB($this->readColor($fillStyleXml->patternFill->fgColor, true)); $defaultFillStyle = Fill::FILL_SOLID; } if ($fillStyleXml->patternFill->bgColor) { $fillStyle->getEndColor()->setARGB($this->readColor($fillStyleXml->patternFill->bgColor, true)); $defaultFillStyle = Fill::FILL_SOLID; } $type = ''; if ((string) $fillStyleXml->patternFill['patternType'] !== '') { $type = (string) $fillStyleXml->patternFill['patternType']; } else { $attr = $this->getStyleAttributes($fillStyleXml->patternFill); $type = (string) $attr['patternType']; } $patternType = ($type === '') ? $defaultFillStyle : $type; $fillStyle->setFillType($patternType); } } public function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void { $diagonalUp = $this->getAttribute($borderStyleXml, 'diagonalUp'); $diagonalUp = self::boolean($diagonalUp); $diagonalDown = $this->getAttribute($borderStyleXml, 'diagonalDown'); $diagonalDown = self::boolean($diagonalDown); if ($diagonalUp === false) { if ($diagonalDown === false) { $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE); } else { $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN); } } elseif ($diagonalDown === false) { $borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP); } else { $borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH); } if (isset($borderStyleXml->left)) { $this->readBorder($borderStyle->getLeft(), $borderStyleXml->left); } if (isset($borderStyleXml->right)) { $this->readBorder($borderStyle->getRight(), $borderStyleXml->right); } if (isset($borderStyleXml->top)) { $this->readBorder($borderStyle->getTop(), $borderStyleXml->top); } if (isset($borderStyleXml->bottom)) { $this->readBorder($borderStyle->getBottom(), $borderStyleXml->bottom); } if (isset($borderStyleXml->diagonal)) { $this->readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal); } } private function getAttribute(SimpleXMLElement $xml, string $attribute): string { $style = ''; if ((string) $xml[$attribute] !== '') { $style = (string) $xml[$attribute]; } else { $attr = $this->getStyleAttributes($xml); if (isset($attr[$attribute])) { $style = (string) $attr[$attribute]; } } return $style; } private function readBorder(Border $border, SimpleXMLElement $borderXml): void { $style = $this->getAttribute($borderXml, 'style'); if ($style !== '') { $border->setBorderStyle((string) $style); } else { $border->setBorderStyle(Border::BORDER_NONE); } if (isset($borderXml->color)) { $border->getColor()->setARGB($this->readColor($borderXml->color)); } } public function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void { $horizontal = (string) $this->getAttribute($alignmentXml, 'horizontal'); if ($horizontal !== '') { $alignment->setHorizontal($horizontal); } $vertical = (string) $this->getAttribute($alignmentXml, 'vertical'); if ($vertical !== '') { $alignment->setVertical($vertical); } $textRotation = (int) $this->getAttribute($alignmentXml, 'textRotation'); if ($textRotation > 90) { $textRotation = 90 - $textRotation; } $alignment->setTextRotation($textRotation); $wrapText = $this->getAttribute($alignmentXml, 'wrapText'); $alignment->setWrapText(self::boolean((string) $wrapText)); $shrinkToFit = $this->getAttribute($alignmentXml, 'shrinkToFit'); $alignment->setShrinkToFit(self::boolean((string) $shrinkToFit)); $indent = (int) $this->getAttribute($alignmentXml, 'indent'); $alignment->setIndent(max($indent, 0)); $readingOrder = (int) $this->getAttribute($alignmentXml, 'readingOrder'); $alignment->setReadOrder(max($readingOrder, 0)); } private static function formatGeneral(string $formatString): string { if ($formatString === 'GENERAL') { $formatString = NumberFormat::FORMAT_GENERAL; } return $formatString; } /** * Read style. * * @param SimpleXMLElement|stdClass $style */ public function readStyle(Style $docStyle, $style): void { if ($style instanceof SimpleXMLElement) { $this->readNumberFormat($docStyle->getNumberFormat(), $style->numFmt); } else { $docStyle->getNumberFormat()->setFormatCode(self::formatGeneral((string) $style->numFmt)); } if (isset($style->font)) { $this->readFontStyle($docStyle->getFont(), $style->font); } if (isset($style->fill)) { $this->readFillStyle($docStyle->getFill(), $style->fill); } if (isset($style->border)) { $this->readBorderStyle($docStyle->getBorders(), $style->border); } if (isset($style->alignment)) { $this->readAlignmentStyle($docStyle->getAlignment(), $style->alignment); } // protection if (isset($style->protection)) { $this->readProtectionLocked($docStyle, $style->protection); $this->readProtectionHidden($docStyle, $style->protection); } // top-level style settings if (isset($style->quotePrefix)) { $docStyle->setQuotePrefix((bool) $style->quotePrefix); } } /** * Read protection locked attribute. */ public function readProtectionLocked(Style $docStyle, SimpleXMLElement $style): void { $locked = ''; if ((string) $style['locked'] !== '') { $locked = (string) $style['locked']; } else { $attr = $this->getStyleAttributes($style); if (isset($attr['locked'])) { $locked = (string) $attr['locked']; } } if ($locked !== '') { if (self::boolean($locked)) { $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED); } else { $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED); } } } /** * Read protection hidden attribute. */ public function readProtectionHidden(Style $docStyle, SimpleXMLElement $style): void { $hidden = ''; if ((string) $style['hidden'] !== '') { $hidden = (string) $style['hidden']; } else { $attr = $this->getStyleAttributes($style); if (isset($attr['hidden'])) { $hidden = (string) $attr['hidden']; } } if ($hidden !== '') { if (self::boolean((string) $hidden)) { $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED); } else { $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED); } } } public function readColor(SimpleXMLElement $color, bool $background = false): string { $attr = $this->getStyleAttributes($color); if (isset($attr['rgb'])) { return (string) $attr['rgb']; } if (isset($attr['indexed'])) { $indexedColor = (int) $attr['indexed']; if ($indexedColor >= count($this->workbookPalette)) { return Color::indexedColor($indexedColor - 7, $background)->getARGB() ?? ''; } return Color::indexedColor($indexedColor, $background, $this->workbookPalette)->getARGB() ?? ''; } if (isset($attr['theme'])) { if ($this->theme !== null) { $returnColour = $this->theme->getColourByIndex((int) $attr['theme']); if (isset($attr['tint'])) { $tintAdjust = (float) $attr['tint']; $returnColour = Color::changeBrightness($returnColour ?? '', $tintAdjust); } return 'FF' . $returnColour; } } return ($background) ? 'FFFFFFFF' : 'FF000000'; } public function dxfs(bool $readDataOnly = false): array { $dxfs = []; if (!$readDataOnly && $this->styleXml) { // Conditional Styles if ($this->styleXml->dxfs) { foreach ($this->styleXml->dxfs->dxf as $dxf) { $style = new Style(false, true); $this->readStyle($style, $dxf); $dxfs[] = $style; } } // Cell Styles if ($this->styleXml->cellStyles) { foreach ($this->styleXml->cellStyles->cellStyle as $cellStylex) { $cellStyle = Xlsx::getAttributes($cellStylex); if ((int) ($cellStyle['builtinId']) == 0) { if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) { // Set default style $style = new Style(); $this->readStyle($style, $this->cellStyles[(int) ($cellStyle['xfId'])]); // normal style, currently not using it for anything } } } } } return $dxfs; } public function styles(): array { return $this->styles; } /** * Get array item. * * @param mixed $array (usually array, in theory can be false) * * @return stdClass */ private static function getArrayItem($array, int $key = 0) { return is_array($array) ? ($array[$key] ?? null) : null; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php000064400000013205152427537250020607 0ustar00spreadsheet = $spreadsheet; } /** * @param mixed $mainNS */ public function viewSettings(SimpleXMLElement $xmlWorkbook, $mainNS, array $mapSheetId, bool $readDataOnly): void { if ($this->spreadsheet->getSheetCount() == 0) { $this->spreadsheet->createSheet(); } // Default active sheet index to the first loaded worksheet from the file $this->spreadsheet->setActiveSheetIndex(0); $workbookView = $xmlWorkbook->children($mainNS)->bookViews->workbookView; if ($readDataOnly !== true && !empty($workbookView)) { $workbookViewAttributes = self::testSimpleXml(self::getAttributes($workbookView)); // active sheet index $activeTab = (int) $workbookViewAttributes->activeTab; // refers to old sheet index // keep active sheet index if sheet is still loaded, else first sheet is set as the active worksheet if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) { $this->spreadsheet->setActiveSheetIndex($mapSheetId[$activeTab]); } $this->horizontalScroll($workbookViewAttributes); $this->verticalScroll($workbookViewAttributes); $this->sheetTabs($workbookViewAttributes); $this->minimized($workbookViewAttributes); $this->autoFilterDateGrouping($workbookViewAttributes); $this->firstSheet($workbookViewAttributes); $this->visibility($workbookViewAttributes); $this->tabRatio($workbookViewAttributes); } } /** * @param mixed $value */ public static function testSimpleXml($value): SimpleXMLElement { return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement(''); } public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement { return self::testSimpleXml($value === null ? $value : $value->attributes($ns)); } /** * Convert an 'xsd:boolean' XML value to a PHP boolean value. * A valid 'xsd:boolean' XML value can be one of the following * four values: 'true', 'false', '1', '0'. It is case sensitive. * * Note that just doing '(bool) $xsdBoolean' is not safe, * since '(bool) "false"' returns true. * * @see https://www.w3.org/TR/xmlschema11-2/#boolean * * @param string $xsdBoolean An XML string value of type 'xsd:boolean' * * @return bool Boolean value */ private function castXsdBooleanToBool(string $xsdBoolean): bool { if ($xsdBoolean === 'false') { return false; } return (bool) $xsdBoolean; } private function horizontalScroll(SimpleXMLElement $workbookViewAttributes): void { if (isset($workbookViewAttributes->showHorizontalScroll)) { $showHorizontalScroll = (string) $workbookViewAttributes->showHorizontalScroll; $this->spreadsheet->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll)); } } private function verticalScroll(SimpleXMLElement $workbookViewAttributes): void { if (isset($workbookViewAttributes->showVerticalScroll)) { $showVerticalScroll = (string) $workbookViewAttributes->showVerticalScroll; $this->spreadsheet->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll)); } } private function sheetTabs(SimpleXMLElement $workbookViewAttributes): void { if (isset($workbookViewAttributes->showSheetTabs)) { $showSheetTabs = (string) $workbookViewAttributes->showSheetTabs; $this->spreadsheet->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs)); } } private function minimized(SimpleXMLElement $workbookViewAttributes): void { if (isset($workbookViewAttributes->minimized)) { $minimized = (string) $workbookViewAttributes->minimized; $this->spreadsheet->setMinimized($this->castXsdBooleanToBool($minimized)); } } private function autoFilterDateGrouping(SimpleXMLElement $workbookViewAttributes): void { if (isset($workbookViewAttributes->autoFilterDateGrouping)) { $autoFilterDateGrouping = (string) $workbookViewAttributes->autoFilterDateGrouping; $this->spreadsheet->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping)); } } private function firstSheet(SimpleXMLElement $workbookViewAttributes): void { if (isset($workbookViewAttributes->firstSheet)) { $firstSheet = (string) $workbookViewAttributes->firstSheet; $this->spreadsheet->setFirstSheetIndex((int) $firstSheet); } } private function visibility(SimpleXMLElement $workbookViewAttributes): void { if (isset($workbookViewAttributes->visibility)) { $visibility = (string) $workbookViewAttributes->visibility; $this->spreadsheet->setVisibility($visibility); } } private function tabRatio(SimpleXMLElement $workbookViewAttributes): void { if (isset($workbookViewAttributes->tabRatio)) { $tabRatio = (string) $workbookViewAttributes->tabRatio; $this->spreadsheet->setTabRatio((int) $tabRatio); } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php000064400000004331152427537250020307 0ustar00worksheet = $workSheet; } public function readHyperlinks(SimpleXMLElement $relsWorksheet): void { foreach ($relsWorksheet->children(Namespaces::RELATIONSHIPS)->Relationship as $elementx) { $element = Xlsx::getAttributes($elementx); if ($element->Type == Namespaces::HYPERLINK) { $this->hyperlinks[(string) $element->Id] = (string) $element->Target; } } } public function setHyperlinks(SimpleXMLElement $worksheetXml): void { foreach ($worksheetXml->children(Namespaces::MAIN)->hyperlink as $hyperlink) { if ($hyperlink !== null) { $this->setHyperlink($hyperlink, $this->worksheet); } } } private function setHyperlink(SimpleXMLElement $hyperlink, Worksheet $worksheet): void { // Link url $linkRel = Xlsx::getAttributes($hyperlink, Namespaces::SCHEMA_OFFICE_DOCUMENT); $attributes = Xlsx::getAttributes($hyperlink); foreach (Coordinate::extractAllCellReferencesInRange($attributes->ref) as $cellReference) { $cell = $worksheet->getCell($cellReference); if (isset($linkRel['id'])) { $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']] ?? null; if (isset($attributes['location'])) { $hyperlinkUrl .= '#' . (string) $attributes['location']; } $cell->getHyperlink()->setUrl($hyperlinkUrl); } elseif (isset($attributes['location'])) { $cell->getHyperlink()->setUrl('sheet://' . (string) $attributes['location']); } // Tooltip if (isset($attributes['tooltip'])) { $cell->getHyperlink()->setTooltip((string) $attributes['tooltip']); } } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php000064400000006243152427537250021232 0ustar00worksheet = $workSheet; $this->worksheetXml = $worksheetXml; } public function load(): void { foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) { // Uppercase coordinate $range = strtoupper((string) $dataValidation['sqref']); $rangeSet = explode(' ', $range); foreach ($rangeSet as $range) { if (preg_match('/^[A-Z]{1,3}\\d{1,7}/', $range, $matches) === 1) { // Ensure left/top row of range exists, thereby // adjusting high row/column. $this->worksheet->getCell($matches[0]); } } } foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) { // Uppercase coordinate $range = strtoupper((string) $dataValidation['sqref']); $rangeSet = explode(' ', $range); foreach ($rangeSet as $range) { $stRange = $this->worksheet->shrinkRangeToFit($range); // Extract all cell references in $range foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $reference) { // Create validation $docValidation = $this->worksheet->getCell($reference)->getDataValidation(); $docValidation->setType((string) $dataValidation['type']); $docValidation->setErrorStyle((string) $dataValidation['errorStyle']); $docValidation->setOperator((string) $dataValidation['operator']); $docValidation->setAllowBlank(filter_var($dataValidation['allowBlank'], FILTER_VALIDATE_BOOLEAN)); // showDropDown is inverted (works as hideDropDown if true) $docValidation->setShowDropDown(!filter_var($dataValidation['showDropDown'], FILTER_VALIDATE_BOOLEAN)); $docValidation->setShowInputMessage(filter_var($dataValidation['showInputMessage'], FILTER_VALIDATE_BOOLEAN)); $docValidation->setShowErrorMessage(filter_var($dataValidation['showErrorMessage'], FILTER_VALIDATE_BOOLEAN)); $docValidation->setErrorTitle((string) $dataValidation['errorTitle']); $docValidation->setError((string) $dataValidation['error']); $docValidation->setPromptTitle((string) $dataValidation['promptTitle']); $docValidation->setPrompt((string) $dataValidation['prompt']); $docValidation->setFormula1((string) $dataValidation->formula1); $docValidation->setFormula2((string) $dataValidation->formula2); $docValidation->setSqref($range); } } } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php000064400000000655152427537250021201 0ustar00worksheet = $workSheet; $this->tableXml = $tableXml; } /** * Loads Table into the Worksheet. */ public function load(): void { // Remove all "$" in the table range $tableRange = (string) preg_replace('/\$/', '', $this->tableXml['ref'] ?? ''); if (strpos($tableRange, ':') !== false) { $this->readTable($tableRange, $this->tableXml); } } /** * Read Table from xml. */ private function readTable(string $tableRange, SimpleXMLElement $tableXml): void { $table = new Table($tableRange); $table->setName((string) $tableXml['displayName']); $table->setShowHeaderRow((string) $tableXml['headerRowCount'] !== '0'); $table->setShowTotalsRow((string) $tableXml['totalsRowCount'] === '1'); $this->readTableAutoFilter($table, $tableXml->autoFilter); $this->readTableColumns($table, $tableXml->tableColumns); $this->readTableStyle($table, $tableXml->tableStyleInfo); (new AutoFilter($table, $tableXml))->load(); $this->worksheet->addTable($table); } /** * Reads TableAutoFilter from xml. */ private function readTableAutoFilter(Table $table, SimpleXMLElement $autoFilterXml): void { if ($autoFilterXml->filterColumn === null) { $table->setAllowFilter(false); return; } foreach ($autoFilterXml->filterColumn as $filterColumn) { $column = $table->getColumnByOffset((int) $filterColumn['colId']); $column->setShowFilterButton((string) $filterColumn['hiddenButton'] !== '1'); } } /** * Reads TableColumns from xml. */ private function readTableColumns(Table $table, SimpleXMLElement $tableColumnsXml): void { $offset = 0; foreach ($tableColumnsXml->tableColumn as $tableColumn) { $column = $table->getColumnByOffset($offset++); if ($table->getShowTotalsRow()) { if ($tableColumn['totalsRowLabel']) { $column->setTotalsRowLabel((string) $tableColumn['totalsRowLabel']); } if ($tableColumn['totalsRowFunction']) { $column->setTotalsRowFunction((string) $tableColumn['totalsRowFunction']); } } if ($tableColumn->calculatedColumnFormula) { $column->setColumnFormula((string) $tableColumn->calculatedColumnFormula); } } } /** * Reads TableStyle from xml. */ private function readTableStyle(Table $table, SimpleXMLElement $tableStyleInfoXml): void { $tableStyle = new TableStyle(); $tableStyle->setTheme((string) $tableStyleInfoXml['name']); $tableStyle->setShowRowStripes((string) $tableStyleInfoXml['showRowStripes'] === '1'); $tableStyle->setShowColumnStripes((string) $tableStyleInfoXml['showColumnStripes'] === '1'); $tableStyle->setShowFirstColumn((string) $tableStyleInfoXml['showFirstColumn'] === '1'); $tableStyle->setShowLastColumn((string) $tableStyleInfoXml['showLastColumn'] === '1'); $table->setStyle($tableStyle); } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php000064400000011337152427537250021442 0ustar00worksheet = $workSheet; $this->worksheetXml = $worksheetXml; } public function load(bool $readDataOnly, Styles $styleReader): void { if ($this->worksheetXml === null) { return; } if (isset($this->worksheetXml->sheetPr)) { $sheetPr = $this->worksheetXml->sheetPr; $this->tabColor($sheetPr, $styleReader); $this->codeName($sheetPr); $this->outlines($sheetPr); $this->pageSetup($sheetPr); } if (isset($this->worksheetXml->sheetFormatPr)) { $this->sheetFormat($this->worksheetXml->sheetFormatPr); } if (!$readDataOnly && isset($this->worksheetXml->printOptions)) { $this->printOptions($this->worksheetXml->printOptions); } } private function tabColor(SimpleXMLElement $sheetPr, Styles $styleReader): void { if (isset($sheetPr->tabColor)) { $this->worksheet->getTabColor()->setARGB($styleReader->readColor($sheetPr->tabColor)); } } private function codeName(SimpleXMLElement $sheetPrx): void { $sheetPr = $sheetPrx->attributes() ?? []; if (isset($sheetPr['codeName'])) { $this->worksheet->setCodeName((string) $sheetPr['codeName'], false); } } private function outlines(SimpleXMLElement $sheetPr): void { if (isset($sheetPr->outlinePr)) { $attr = $sheetPr->outlinePr->attributes() ?? []; if ( isset($attr['summaryRight']) && !self::boolean((string) $attr['summaryRight']) ) { $this->worksheet->setShowSummaryRight(false); } else { $this->worksheet->setShowSummaryRight(true); } if ( isset($attr['summaryBelow']) && !self::boolean((string) $attr['summaryBelow']) ) { $this->worksheet->setShowSummaryBelow(false); } else { $this->worksheet->setShowSummaryBelow(true); } } } private function pageSetup(SimpleXMLElement $sheetPr): void { if (isset($sheetPr->pageSetUpPr)) { $attr = $sheetPr->pageSetUpPr->attributes() ?? []; if ( isset($attr['fitToPage']) && !self::boolean((string) $attr['fitToPage']) ) { $this->worksheet->getPageSetup()->setFitToPage(false); } else { $this->worksheet->getPageSetup()->setFitToPage(true); } } } private function sheetFormat(SimpleXMLElement $sheetFormatPrx): void { $sheetFormatPr = $sheetFormatPrx->attributes() ?? []; if ( isset($sheetFormatPr['customHeight']) && self::boolean((string) $sheetFormatPr['customHeight']) && isset($sheetFormatPr['defaultRowHeight']) ) { $this->worksheet->getDefaultRowDimension() ->setRowHeight((float) $sheetFormatPr['defaultRowHeight']); } if (isset($sheetFormatPr['defaultColWidth'])) { $this->worksheet->getDefaultColumnDimension() ->setWidth((float) $sheetFormatPr['defaultColWidth']); } if ( isset($sheetFormatPr['zeroHeight']) && ((string) $sheetFormatPr['zeroHeight'] === '1') ) { $this->worksheet->getDefaultRowDimension()->setZeroHeight(true); } } private function printOptions(SimpleXMLElement $printOptionsx): void { $printOptions = $printOptionsx->attributes() ?? []; if (isset($printOptions['gridLinesSet']) && self::boolean((string) $printOptions['gridLinesSet'])) { $this->worksheet->setShowGridlines(true); } if (isset($printOptions['gridLines']) && self::boolean((string) $printOptions['gridLines'])) { $this->worksheet->setPrintGridlines(true); } if (isset($printOptions['horizontalCentered']) && self::boolean((string) $printOptions['horizontalCentered'])) { $this->worksheet->getPageSetup()->setHorizontalCentered(true); } if (isset($printOptions['verticalCentered']) && self::boolean((string) $printOptions['verticalCentered'])) { $this->worksheet->getPageSetup()->setVerticalCentered(true); } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php000064400000230535152427537250017227 0ustar00cNamespace = $cNamespace; $this->aNamespace = $aNamespace; } /** * @param string $name * @param string $format * * @return null|bool|float|int|string */ private static function getAttribute(SimpleXMLElement $component, $name, $format) { $attributes = $component->attributes(); if (@isset($attributes[$name])) { if ($format == 'string') { return (string) $attributes[$name]; } elseif ($format == 'integer') { return (int) $attributes[$name]; } elseif ($format == 'boolean') { $value = (string) $attributes[$name]; return $value === 'true' || $value === '1'; } return (float) $attributes[$name]; } return null; } /** * @param string $chartName * * @return \PhpOffice\PhpSpreadsheet\Chart\Chart */ public function readChart(SimpleXMLElement $chartElements, $chartName) { $chartElementsC = $chartElements->children($this->cNamespace); $XaxisLabel = $YaxisLabel = $legend = $title = null; $dispBlanksAs = $plotVisOnly = null; $plotArea = null; $rotX = $rotY = $rAngAx = $perspective = null; $xAxis = new Axis(); $yAxis = new Axis(); $autoTitleDeleted = null; $chartNoFill = false; $chartBorderLines = null; $chartFillColor = null; $gradientArray = []; $gradientLin = null; $roundedCorners = false; $gapWidth = null; $useUpBars = null; $useDownBars = null; foreach ($chartElementsC as $chartElementKey => $chartElement) { switch ($chartElementKey) { case 'spPr': $children = $chartElementsC->spPr->children($this->aNamespace); if (isset($children->noFill)) { $chartNoFill = true; } if (isset($children->solidFill)) { $chartFillColor = $this->readColor($children->solidFill); } if (isset($children->ln)) { $chartBorderLines = new GridLines(); $this->readLineStyle($chartElementsC, $chartBorderLines); } break; case 'roundedCorners': /** @var bool */ $roundedCorners = self::getAttribute($chartElementsC->roundedCorners, 'val', 'boolean'); break; case 'chart': foreach ($chartElement as $chartDetailsKey => $chartDetails) { $chartDetails = Xlsx::testSimpleXml($chartDetails); switch ($chartDetailsKey) { case 'autoTitleDeleted': /** @var bool */ $autoTitleDeleted = self::getAttribute($chartElementsC->chart->autoTitleDeleted, 'val', 'boolean'); break; case 'view3D': $rotX = self::getAttribute($chartDetails->rotX, 'val', 'integer'); $rotY = self::getAttribute($chartDetails->rotY, 'val', 'integer'); $rAngAx = self::getAttribute($chartDetails->rAngAx, 'val', 'integer'); $perspective = self::getAttribute($chartDetails->perspective, 'val', 'integer'); break; case 'plotArea': $plotAreaLayout = $XaxisLabel = $YaxisLabel = null; $plotSeries = $plotAttributes = []; $catAxRead = false; $plotNoFill = false; foreach ($chartDetails as $chartDetailKey => $chartDetail) { $chartDetail = Xlsx::testSimpleXml($chartDetail); switch ($chartDetailKey) { case 'spPr': $possibleNoFill = $chartDetails->spPr->children($this->aNamespace); if (isset($possibleNoFill->noFill)) { $plotNoFill = true; } if (isset($possibleNoFill->gradFill->gsLst)) { foreach ($possibleNoFill->gradFill->gsLst->gs as $gradient) { $gradient = Xlsx::testSimpleXml($gradient); /** @var float */ $pos = self::getAttribute($gradient, 'pos', 'float'); $gradientArray[] = [ $pos / ChartProperties::PERCENTAGE_MULTIPLIER, new ChartColor($this->readColor($gradient)), ]; } } if (isset($possibleNoFill->gradFill->lin)) { $gradientLin = ChartProperties::XmlToAngle((string) self::getAttribute($possibleNoFill->gradFill->lin, 'ang', 'string')); } break; case 'layout': $plotAreaLayout = $this->chartLayoutDetails($chartDetail); break; case Axis::AXIS_TYPE_CATEGORY: case Axis::AXIS_TYPE_DATE: $catAxRead = true; if (isset($chartDetail->title)) { $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); } $xAxis->setAxisType($chartDetailKey); $this->readEffects($chartDetail, $xAxis); $this->readLineStyle($chartDetail, $xAxis); if (isset($chartDetail->spPr)) { $sppr = $chartDetail->spPr->children($this->aNamespace); if (isset($sppr->solidFill)) { $axisColorArray = $this->readColor($sppr->solidFill); $xAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']); } if (isset($chartDetail->spPr->ln->noFill)) { $xAxis->setNoFill(true); } } if (isset($chartDetail->majorGridlines)) { $majorGridlines = new GridLines(); if (isset($chartDetail->majorGridlines->spPr)) { $this->readEffects($chartDetail->majorGridlines, $majorGridlines); $this->readLineStyle($chartDetail->majorGridlines, $majorGridlines); } $xAxis->setMajorGridlines($majorGridlines); } if (isset($chartDetail->minorGridlines)) { $minorGridlines = new GridLines(); $minorGridlines->activateObject(); if (isset($chartDetail->minorGridlines->spPr)) { $this->readEffects($chartDetail->minorGridlines, $minorGridlines); $this->readLineStyle($chartDetail->minorGridlines, $minorGridlines); } $xAxis->setMinorGridlines($minorGridlines); } $this->setAxisProperties($chartDetail, $xAxis); break; case Axis::AXIS_TYPE_VALUE: $whichAxis = null; $axPos = null; if (isset($chartDetail->axPos)) { $axPos = self::getAttribute($chartDetail->axPos, 'val', 'string'); } if ($catAxRead) { $whichAxis = $yAxis; $yAxis->setAxisType($chartDetailKey); } elseif (!empty($axPos)) { switch ($axPos) { case 't': case 'b': $whichAxis = $xAxis; $xAxis->setAxisType($chartDetailKey); break; case 'r': case 'l': $whichAxis = $yAxis; $yAxis->setAxisType($chartDetailKey); break; } } if (isset($chartDetail->title)) { $axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); switch ($axPos) { case 't': case 'b': $XaxisLabel = $axisLabel; break; case 'r': case 'l': $YaxisLabel = $axisLabel; break; } } $this->readEffects($chartDetail, $whichAxis); $this->readLineStyle($chartDetail, $whichAxis); if ($whichAxis !== null && isset($chartDetail->spPr)) { $sppr = $chartDetail->spPr->children($this->aNamespace); if (isset($sppr->solidFill)) { $axisColorArray = $this->readColor($sppr->solidFill); $whichAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']); } if (isset($sppr->ln->noFill)) { $whichAxis->setNoFill(true); } } if ($whichAxis !== null && isset($chartDetail->majorGridlines)) { $majorGridlines = new GridLines(); if (isset($chartDetail->majorGridlines->spPr)) { $this->readEffects($chartDetail->majorGridlines, $majorGridlines); $this->readLineStyle($chartDetail->majorGridlines, $majorGridlines); } $whichAxis->setMajorGridlines($majorGridlines); } if ($whichAxis !== null && isset($chartDetail->minorGridlines)) { $minorGridlines = new GridLines(); $minorGridlines->activateObject(); if (isset($chartDetail->minorGridlines->spPr)) { $this->readEffects($chartDetail->minorGridlines, $minorGridlines); $this->readLineStyle($chartDetail->minorGridlines, $minorGridlines); } $whichAxis->setMinorGridlines($minorGridlines); } $this->setAxisProperties($chartDetail, $whichAxis); break; case 'barChart': case 'bar3DChart': $barDirection = self::getAttribute($chartDetail->barDir, 'val', 'string'); $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); $plotSer->setPlotDirection("$barDirection"); $plotSeries[] = $plotSer; $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'lineChart': case 'line3DChart': $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey); $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'areaChart': case 'area3DChart': $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey); $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'doughnutChart': case 'pieChart': case 'pie3DChart': $explosion = self::getAttribute($chartDetail->ser->explosion, 'val', 'string'); $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); $plotSer->setPlotStyle("$explosion"); $plotSeries[] = $plotSer; $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'scatterChart': /** @var string */ $scatterStyle = self::getAttribute($chartDetail->scatterStyle, 'val', 'string'); $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); $plotSer->setPlotStyle($scatterStyle); $plotSeries[] = $plotSer; $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'bubbleChart': $bubbleScale = self::getAttribute($chartDetail->bubbleScale, 'val', 'integer'); $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); $plotSer->setPlotStyle("$bubbleScale"); $plotSeries[] = $plotSer; $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'radarChart': /** @var string */ $radarStyle = self::getAttribute($chartDetail->radarStyle, 'val', 'string'); $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); $plotSer->setPlotStyle($radarStyle); $plotSeries[] = $plotSer; $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'surfaceChart': case 'surface3DChart': $wireFrame = self::getAttribute($chartDetail->wireframe, 'val', 'boolean'); $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); $plotSer->setPlotStyle("$wireFrame"); $plotSeries[] = $plotSer; $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'stockChart': $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey); if (isset($chartDetail->upDownBars->gapWidth)) { $gapWidth = self::getAttribute($chartDetail->upDownBars->gapWidth, 'val', 'integer'); } if (isset($chartDetail->upDownBars->upBars)) { $useUpBars = true; } if (isset($chartDetail->upDownBars->downBars)) { $useDownBars = true; } $plotAttributes = $this->readChartAttributes($chartDetail); break; } } if ($plotAreaLayout == null) { $plotAreaLayout = new Layout(); } $plotArea = new PlotArea($plotAreaLayout, $plotSeries); $this->setChartAttributes($plotAreaLayout, $plotAttributes); if ($plotNoFill) { $plotArea->setNoFill(true); } if (!empty($gradientArray)) { $plotArea->setGradientFillProperties($gradientArray, $gradientLin); } if (is_int($gapWidth)) { $plotArea->setGapWidth($gapWidth); } if ($useUpBars === true) { $plotArea->setUseUpBars(true); } if ($useDownBars === true) { $plotArea->setUseDownBars(true); } break; case 'plotVisOnly': $plotVisOnly = self::getAttribute($chartDetails, 'val', 'string'); break; case 'dispBlanksAs': $dispBlanksAs = self::getAttribute($chartDetails, 'val', 'string'); break; case 'title': $title = $this->chartTitle($chartDetails); break; case 'legend': $legendPos = 'r'; $legendLayout = null; $legendOverlay = false; $legendBorderLines = null; $legendFillColor = null; $legendText = null; $addLegendText = false; foreach ($chartDetails as $chartDetailKey => $chartDetail) { $chartDetail = Xlsx::testSimpleXml($chartDetail); switch ($chartDetailKey) { case 'legendPos': $legendPos = self::getAttribute($chartDetail, 'val', 'string'); break; case 'overlay': $legendOverlay = self::getAttribute($chartDetail, 'val', 'boolean'); break; case 'layout': $legendLayout = $this->chartLayoutDetails($chartDetail); break; case 'spPr': $children = $chartDetails->spPr->children($this->aNamespace); if (isset($children->solidFill)) { $legendFillColor = $this->readColor($children->solidFill); } if (isset($children->ln)) { $legendBorderLines = new GridLines(); $this->readLineStyle($chartDetails, $legendBorderLines); } break; case 'txPr': $children = $chartDetails->txPr->children($this->aNamespace); $addLegendText = false; $legendText = new AxisText(); if (isset($children->p->pPr->defRPr->solidFill)) { $colorArray = $this->readColor($children->p->pPr->defRPr->solidFill); $legendText->getFillColorObject()->setColorPropertiesArray($colorArray); $addLegendText = true; } if (isset($children->p->pPr->defRPr->effectLst)) { $this->readEffects($children->p->pPr->defRPr, $legendText, false); $addLegendText = true; } break; } } $legend = new Legend("$legendPos", $legendLayout, (bool) $legendOverlay); if ($legendFillColor !== null) { $legend->getFillColor()->setColorPropertiesArray($legendFillColor); } if ($legendBorderLines !== null) { $legend->setBorderLines($legendBorderLines); } if ($addLegendText) { $legend->setLegendText($legendText); } break; } } } } $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis); if ($chartNoFill) { $chart->setNoFill(true); } if ($chartFillColor !== null) { $chart->getFillColor()->setColorPropertiesArray($chartFillColor); } if ($chartBorderLines !== null) { $chart->setBorderLines($chartBorderLines); } $chart->setRoundedCorners($roundedCorners); if (is_bool($autoTitleDeleted)) { $chart->setAutoTitleDeleted($autoTitleDeleted); } if (is_int($rotX)) { $chart->setRotX($rotX); } if (is_int($rotY)) { $chart->setRotY($rotY); } if (is_int($rAngAx)) { $chart->setRAngAx($rAngAx); } if (is_int($perspective)) { $chart->setPerspective($perspective); } return $chart; } private function chartTitle(SimpleXMLElement $titleDetails): Title { $caption = []; $titleLayout = null; $titleOverlay = false; foreach ($titleDetails as $titleDetailKey => $chartDetail) { $chartDetail = Xlsx::testSimpleXml($chartDetail); switch ($titleDetailKey) { case 'tx': if (isset($chartDetail->rich)) { $titleDetails = $chartDetail->rich->children($this->aNamespace); foreach ($titleDetails as $titleKey => $titleDetail) { $titleDetail = Xlsx::testSimpleXml($titleDetail); switch ($titleKey) { case 'p': $titleDetailPart = $titleDetail->children($this->aNamespace); $caption[] = $this->parseRichText($titleDetailPart); } } } elseif (isset($chartDetail->strRef->strCache)) { foreach ($chartDetail->strRef->strCache->pt as $pt) { if (isset($pt->v)) { $caption[] = (string) $pt->v; } } } break; case 'overlay': $titleOverlay = self::getAttribute($chartDetail, 'val', 'boolean'); break; case 'layout': $titleLayout = $this->chartLayoutDetails($chartDetail); break; } } return new Title($caption, $titleLayout, (bool) $titleOverlay); } private function chartLayoutDetails(SimpleXMLElement $chartDetail): ?Layout { if (!isset($chartDetail->manualLayout)) { return null; } $details = $chartDetail->manualLayout->children($this->cNamespace); if ($details === null) { return null; } $layout = []; foreach ($details as $detailKey => $detail) { $detail = Xlsx::testSimpleXml($detail); $layout[$detailKey] = self::getAttribute($detail, 'val', 'string'); } return new Layout($layout); } private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType): DataSeries { $multiSeriesType = null; $smoothLine = false; $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = $seriesBubbles = []; $seriesDetailSet = $chartDetail->children($this->cNamespace); foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) { switch ($seriesDetailKey) { case 'grouping': $multiSeriesType = self::getAttribute($chartDetail->grouping, 'val', 'string'); break; case 'ser': $marker = null; $seriesIndex = ''; $fillColor = null; $pointSize = null; $noFill = false; $bubble3D = false; $dptColors = []; $markerFillColor = null; $markerBorderColor = null; $lineStyle = null; $labelLayout = null; $trendLines = []; foreach ($seriesDetails as $seriesKey => $seriesDetail) { $seriesDetail = Xlsx::testSimpleXml($seriesDetail); switch ($seriesKey) { case 'idx': $seriesIndex = self::getAttribute($seriesDetail, 'val', 'integer'); break; case 'order': $seriesOrder = self::getAttribute($seriesDetail, 'val', 'integer'); $plotOrder[$seriesIndex] = $seriesOrder; break; case 'tx': $seriesLabel[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail); break; case 'spPr': $children = $seriesDetail->children($this->aNamespace); if (isset($children->ln)) { $ln = $children->ln; if (is_countable($ln->noFill) && count($ln->noFill) === 1) { $noFill = true; } $lineStyle = new GridLines(); $this->readLineStyle($seriesDetails, $lineStyle); } if (isset($children->effectLst)) { if ($lineStyle === null) { $lineStyle = new GridLines(); } $this->readEffects($seriesDetails, $lineStyle); } if (isset($children->solidFill)) { $fillColor = new ChartColor($this->readColor($children->solidFill)); } break; case 'dPt': $dptIdx = (int) self::getAttribute($seriesDetail->idx, 'val', 'string'); if (isset($seriesDetail->spPr)) { $children = $seriesDetail->spPr->children($this->aNamespace); if (isset($children->solidFill)) { $arrayColors = $this->readColor($children->solidFill); $dptColors[$dptIdx] = new ChartColor($arrayColors); } } break; case 'trendline': $trendLine = new TrendLine(); $this->readLineStyle($seriesDetail, $trendLine); /** @var ?string */ $trendLineType = self::getAttribute($seriesDetail->trendlineType, 'val', 'string'); /** @var ?bool */ $dispRSqr = self::getAttribute($seriesDetail->dispRSqr, 'val', 'boolean'); /** @var ?bool */ $dispEq = self::getAttribute($seriesDetail->dispEq, 'val', 'boolean'); /** @var ?int */ $order = self::getAttribute($seriesDetail->order, 'val', 'integer'); /** @var ?int */ $period = self::getAttribute($seriesDetail->period, 'val', 'integer'); /** @var ?float */ $forward = self::getAttribute($seriesDetail->forward, 'val', 'float'); /** @var ?float */ $backward = self::getAttribute($seriesDetail->backward, 'val', 'float'); /** @var ?float */ $intercept = self::getAttribute($seriesDetail->intercept, 'val', 'float'); /** @var ?string */ $name = (string) $seriesDetail->name; $trendLine->setTrendLineProperties( $trendLineType, $order, $period, $dispRSqr, $dispEq, $backward, $forward, $intercept, $name ); $trendLines[] = $trendLine; break; case 'marker': $marker = self::getAttribute($seriesDetail->symbol, 'val', 'string'); $pointSize = self::getAttribute($seriesDetail->size, 'val', 'string'); $pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null; if (isset($seriesDetail->spPr)) { $children = $seriesDetail->spPr->children($this->aNamespace); if (isset($children->solidFill)) { $markerFillColor = $this->readColor($children->solidFill); } if (isset($children->ln->solidFill)) { $markerBorderColor = $this->readColor($children->ln->solidFill); } } break; case 'smooth': $smoothLine = self::getAttribute($seriesDetail, 'val', 'boolean'); break; case 'cat': $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail); break; case 'val': $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); break; case 'xVal': $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); break; case 'yVal': $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); break; case 'bubbleSize': $seriesBubbles[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); break; case 'bubble3D': $bubble3D = self::getAttribute($seriesDetail, 'val', 'boolean'); break; case 'dLbls': $labelLayout = new Layout($this->readChartAttributes($seriesDetails)); break; } } if ($labelLayout) { if (isset($seriesLabel[$seriesIndex])) { $seriesLabel[$seriesIndex]->setLabelLayout($labelLayout); } if (isset($seriesCategory[$seriesIndex])) { $seriesCategory[$seriesIndex]->setLabelLayout($labelLayout); } if (isset($seriesValues[$seriesIndex])) { $seriesValues[$seriesIndex]->setLabelLayout($labelLayout); } } if ($noFill) { if (isset($seriesLabel[$seriesIndex])) { $seriesLabel[$seriesIndex]->setScatterLines(false); } if (isset($seriesCategory[$seriesIndex])) { $seriesCategory[$seriesIndex]->setScatterLines(false); } if (isset($seriesValues[$seriesIndex])) { $seriesValues[$seriesIndex]->setScatterLines(false); } } if ($lineStyle !== null) { if (isset($seriesLabel[$seriesIndex])) { $seriesLabel[$seriesIndex]->copyLineStyles($lineStyle); } if (isset($seriesCategory[$seriesIndex])) { $seriesCategory[$seriesIndex]->copyLineStyles($lineStyle); } if (isset($seriesValues[$seriesIndex])) { $seriesValues[$seriesIndex]->copyLineStyles($lineStyle); } } if ($bubble3D) { if (isset($seriesLabel[$seriesIndex])) { $seriesLabel[$seriesIndex]->setBubble3D($bubble3D); } if (isset($seriesCategory[$seriesIndex])) { $seriesCategory[$seriesIndex]->setBubble3D($bubble3D); } if (isset($seriesValues[$seriesIndex])) { $seriesValues[$seriesIndex]->setBubble3D($bubble3D); } } if (!empty($dptColors)) { if (isset($seriesLabel[$seriesIndex])) { $seriesLabel[$seriesIndex]->setFillColor($dptColors); } if (isset($seriesCategory[$seriesIndex])) { $seriesCategory[$seriesIndex]->setFillColor($dptColors); } if (isset($seriesValues[$seriesIndex])) { $seriesValues[$seriesIndex]->setFillColor($dptColors); } } if ($markerFillColor !== null) { if (isset($seriesLabel[$seriesIndex])) { $seriesLabel[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor); } if (isset($seriesCategory[$seriesIndex])) { $seriesCategory[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor); } if (isset($seriesValues[$seriesIndex])) { $seriesValues[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor); } } if ($markerBorderColor !== null) { if (isset($seriesLabel[$seriesIndex])) { $seriesLabel[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor); } if (isset($seriesCategory[$seriesIndex])) { $seriesCategory[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor); } if (isset($seriesValues[$seriesIndex])) { $seriesValues[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor); } } if ($smoothLine) { if (isset($seriesLabel[$seriesIndex])) { $seriesLabel[$seriesIndex]->setSmoothLine(true); } if (isset($seriesCategory[$seriesIndex])) { $seriesCategory[$seriesIndex]->setSmoothLine(true); } if (isset($seriesValues[$seriesIndex])) { $seriesValues[$seriesIndex]->setSmoothLine(true); } } if (!empty($trendLines)) { if (isset($seriesLabel[$seriesIndex])) { $seriesLabel[$seriesIndex]->setTrendLines($trendLines); } if (isset($seriesCategory[$seriesIndex])) { $seriesCategory[$seriesIndex]->setTrendLines($trendLines); } if (isset($seriesValues[$seriesIndex])) { $seriesValues[$seriesIndex]->setTrendLines($trendLines); } } } } /** @phpstan-ignore-next-line */ $series = new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine); $series->setPlotBubbleSizes($seriesBubbles); return $series; } /** * @return mixed */ private function chartDataSeriesValueSet(SimpleXMLElement $seriesDetail, ?string $marker = null, ?ChartColor $fillColor = null, ?string $pointSize = null) { if (isset($seriesDetail->strRef)) { $seriesSource = (string) $seriesDetail->strRef->f; $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->strRef->strCache)) { $seriesData = $this->chartDataSeriesValues($seriesDetail->strRef->strCache->children($this->cNamespace), 's'); $seriesValues ->setFormatCode($seriesData['formatCode']) ->setDataValues($seriesData['dataValues']); } return $seriesValues; } elseif (isset($seriesDetail->numRef)) { $seriesSource = (string) $seriesDetail->numRef->f; $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->numRef->numCache)) { $seriesData = $this->chartDataSeriesValues($seriesDetail->numRef->numCache->children($this->cNamespace)); $seriesValues ->setFormatCode($seriesData['formatCode']) ->setDataValues($seriesData['dataValues']); } return $seriesValues; } elseif (isset($seriesDetail->multiLvlStrRef)) { $seriesSource = (string) $seriesDetail->multiLvlStrRef->f; $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) { $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($this->cNamespace), 's'); $seriesValues ->setFormatCode($seriesData['formatCode']) ->setDataValues($seriesData['dataValues']); } return $seriesValues; } elseif (isset($seriesDetail->multiLvlNumRef)) { $seriesSource = (string) $seriesDetail->multiLvlNumRef->f; $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) { $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($this->cNamespace), 's'); $seriesValues ->setFormatCode($seriesData['formatCode']) ->setDataValues($seriesData['dataValues']); } return $seriesValues; } if (isset($seriesDetail->v)) { return new DataSeriesValues( DataSeriesValues::DATASERIES_TYPE_STRING, null, null, 1, [(string) $seriesDetail->v] ); } return null; } private function chartDataSeriesValues(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array { $seriesVal = []; $formatCode = ''; $pointCount = 0; foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) { $seriesValue = Xlsx::testSimpleXml($seriesValue); switch ($seriesValueIdx) { case 'ptCount': $pointCount = self::getAttribute($seriesValue, 'val', 'integer'); break; case 'formatCode': $formatCode = (string) $seriesValue; break; case 'pt': $pointVal = self::getAttribute($seriesValue, 'idx', 'integer'); if ($dataType == 's') { $seriesVal[$pointVal] = (string) $seriesValue->v; } elseif ((string) $seriesValue->v === ExcelError::NA()) { $seriesVal[$pointVal] = null; } else { $seriesVal[$pointVal] = (float) $seriesValue->v; } break; } } return [ 'formatCode' => $formatCode, 'pointCount' => $pointCount, 'dataValues' => $seriesVal, ]; } private function chartDataSeriesValuesMultiLevel(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array { $seriesVal = []; $formatCode = ''; $pointCount = 0; foreach ($seriesValueSet->lvl as $seriesLevelIdx => $seriesLevel) { foreach ($seriesLevel as $seriesValueIdx => $seriesValue) { $seriesValue = Xlsx::testSimpleXml($seriesValue); switch ($seriesValueIdx) { case 'ptCount': $pointCount = self::getAttribute($seriesValue, 'val', 'integer'); break; case 'formatCode': $formatCode = (string) $seriesValue; break; case 'pt': $pointVal = self::getAttribute($seriesValue, 'idx', 'integer'); if ($dataType == 's') { $seriesVal[$pointVal][] = (string) $seriesValue->v; } elseif ((string) $seriesValue->v === ExcelError::NA()) { $seriesVal[$pointVal] = null; } else { $seriesVal[$pointVal][] = (float) $seriesValue->v; } break; } } } return [ 'formatCode' => $formatCode, 'pointCount' => $pointCount, 'dataValues' => $seriesVal, ]; } private function parseRichText(SimpleXMLElement $titleDetailPart): RichText { $value = new RichText(); $defaultFontSize = null; $defaultBold = null; $defaultItalic = null; $defaultUnderscore = null; $defaultStrikethrough = null; $defaultBaseline = null; $defaultFontName = null; $defaultLatin = null; $defaultEastAsian = null; $defaultComplexScript = null; $defaultFontColor = null; if (isset($titleDetailPart->pPr->defRPr)) { /** @var ?int */ $defaultFontSize = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer'); /** @var ?bool */ $defaultBold = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean'); /** @var ?bool */ $defaultItalic = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean'); /** @var ?string */ $defaultUnderscore = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string'); /** @var ?string */ $defaultStrikethrough = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string'); /** @var ?int */ $defaultBaseline = self::getAttribute($titleDetailPart->pPr->defRPr, 'baseline', 'integer'); if (isset($titleDetailPart->defRPr->rFont['val'])) { $defaultFontName = (string) $titleDetailPart->defRPr->rFont['val']; } if (isset($titleDetailPart->pPr->defRPr->latin)) { /** @var ?string */ $defaultLatin = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string'); } if (isset($titleDetailPart->pPr->defRPr->ea)) { /** @var ?string */ $defaultEastAsian = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string'); } if (isset($titleDetailPart->pPr->defRPr->cs)) { /** @var ?string */ $defaultComplexScript = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string'); } if (isset($titleDetailPart->pPr->defRPr->solidFill)) { $defaultFontColor = $this->readColor($titleDetailPart->pPr->defRPr->solidFill); } } foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) { if ( (string) $titleDetailElementKey !== 'r' || !isset($titleDetailElement->t) ) { continue; } $objText = $value->createTextRun((string) $titleDetailElement->t); if ($objText->getFont() === null) { // @codeCoverageIgnoreStart continue; // @codeCoverageIgnoreEnd } $fontSize = null; $bold = null; $italic = null; $underscore = null; $strikethrough = null; $baseline = null; $fontName = null; $latinName = null; $eastAsian = null; $complexScript = null; $fontColor = null; $underlineColor = null; if (isset($titleDetailElement->rPr)) { // not used now, not sure it ever was, grandfathering if (isset($titleDetailElement->rPr->rFont['val'])) { // @codeCoverageIgnoreStart $fontName = (string) $titleDetailElement->rPr->rFont['val']; // @codeCoverageIgnoreEnd } if (isset($titleDetailElement->rPr->latin)) { /** @var ?string */ $latinName = self::getAttribute($titleDetailElement->rPr->latin, 'typeface', 'string'); } if (isset($titleDetailElement->rPr->ea)) { /** @var ?string */ $eastAsian = self::getAttribute($titleDetailElement->rPr->ea, 'typeface', 'string'); } if (isset($titleDetailElement->rPr->cs)) { /** @var ?string */ $complexScript = self::getAttribute($titleDetailElement->rPr->cs, 'typeface', 'string'); } /** @var ?int */ $fontSize = self::getAttribute($titleDetailElement->rPr, 'sz', 'integer'); // not used now, not sure it ever was, grandfathering if (isset($titleDetailElement->rPr->solidFill)) { $fontColor = $this->readColor($titleDetailElement->rPr->solidFill); } /** @var ?bool */ $bold = self::getAttribute($titleDetailElement->rPr, 'b', 'boolean'); /** @var ?bool */ $italic = self::getAttribute($titleDetailElement->rPr, 'i', 'boolean'); /** @var ?int */ $baseline = self::getAttribute($titleDetailElement->rPr, 'baseline', 'integer'); /** @var ?string */ $underscore = self::getAttribute($titleDetailElement->rPr, 'u', 'string'); if (isset($titleDetailElement->rPr->uFill->solidFill)) { $underlineColor = $this->readColor($titleDetailElement->rPr->uFill->solidFill); } /** @var ?string */ $strikethrough = self::getAttribute($titleDetailElement->rPr, 'strike', 'string'); } $fontFound = false; $latinName = $latinName ?? $defaultLatin; if ($latinName !== null) { $objText->getFont()->setLatin($latinName); $fontFound = true; } $eastAsian = $eastAsian ?? $defaultEastAsian; if ($eastAsian !== null) { $objText->getFont()->setEastAsian($eastAsian); $fontFound = true; } $complexScript = $complexScript ?? $defaultComplexScript; if ($complexScript !== null) { $objText->getFont()->setComplexScript($complexScript); $fontFound = true; } $fontName = $fontName ?? $defaultFontName; if ($fontName !== null) { // @codeCoverageIgnoreStart $objText->getFont()->setName($fontName); $fontFound = true; // @codeCoverageIgnoreEnd } $fontSize = $fontSize ?? $defaultFontSize; if (is_int($fontSize)) { $objText->getFont()->setSize(floor($fontSize / 100)); $fontFound = true; } else { $objText->getFont()->setSize(null, true); } $fontColor = $fontColor ?? $defaultFontColor; if (!empty($fontColor)) { $objText->getFont()->setChartColor($fontColor); $fontFound = true; } $bold = $bold ?? $defaultBold; if ($bold !== null) { $objText->getFont()->setBold($bold); $fontFound = true; } $italic = $italic ?? $defaultItalic; if ($italic !== null) { $objText->getFont()->setItalic($italic); $fontFound = true; } $baseline = $baseline ?? $defaultBaseline; if ($baseline !== null) { $objText->getFont()->setBaseLine($baseline); if ($baseline > 0) { $objText->getFont()->setSuperscript(true); } elseif ($baseline < 0) { $objText->getFont()->setSubscript(true); } $fontFound = true; } $underscore = $underscore ?? $defaultUnderscore; if ($underscore !== null) { if ($underscore == 'sng') { $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE); } elseif ($underscore == 'dbl') { $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE); } elseif ($underscore !== '') { $objText->getFont()->setUnderline($underscore); } else { $objText->getFont()->setUnderline(Font::UNDERLINE_NONE); } $fontFound = true; if ($underlineColor) { $objText->getFont()->setUnderlineColor($underlineColor); } } $strikethrough = $strikethrough ?? $defaultStrikethrough; if ($strikethrough !== null) { $objText->getFont()->setStrikeType($strikethrough); if ($strikethrough == 'noStrike') { $objText->getFont()->setStrikethrough(false); } else { $objText->getFont()->setStrikethrough(true); } $fontFound = true; } if ($fontFound === false) { $objText->setFont(null); } } return $value; } private function parseFont(SimpleXMLElement $titleDetailPart): ?Font { if (!isset($titleDetailPart->pPr->defRPr)) { return null; } $fontArray = []; $fontArray['size'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer'); $fontArray['bold'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean'); $fontArray['italic'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean'); $fontArray['underscore'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string'); $fontArray['strikethrough'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string'); if (isset($titleDetailPart->pPr->defRPr->latin)) { $fontArray['latin'] = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string'); } if (isset($titleDetailPart->pPr->defRPr->ea)) { $fontArray['eastAsian'] = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string'); } if (isset($titleDetailPart->pPr->defRPr->cs)) { $fontArray['complexScript'] = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string'); } if (isset($titleDetailPart->pPr->defRPr->solidFill)) { $fontArray['chartColor'] = new ChartColor($this->readColor($titleDetailPart->pPr->defRPr->solidFill)); } $font = new Font(); $font->setSize(null, true); $font->applyFromArray($fontArray); return $font; } /** * @param ?SimpleXMLElement $chartDetail */ private function readChartAttributes($chartDetail): array { $plotAttributes = []; if (isset($chartDetail->dLbls)) { if (isset($chartDetail->dLbls->dLblPos)) { $plotAttributes['dLblPos'] = self::getAttribute($chartDetail->dLbls->dLblPos, 'val', 'string'); } if (isset($chartDetail->dLbls->numFmt)) { $plotAttributes['numFmtCode'] = self::getAttribute($chartDetail->dLbls->numFmt, 'formatCode', 'string'); $plotAttributes['numFmtLinked'] = self::getAttribute($chartDetail->dLbls->numFmt, 'sourceLinked', 'boolean'); } if (isset($chartDetail->dLbls->showLegendKey)) { $plotAttributes['showLegendKey'] = self::getAttribute($chartDetail->dLbls->showLegendKey, 'val', 'string'); } if (isset($chartDetail->dLbls->showVal)) { $plotAttributes['showVal'] = self::getAttribute($chartDetail->dLbls->showVal, 'val', 'string'); } if (isset($chartDetail->dLbls->showCatName)) { $plotAttributes['showCatName'] = self::getAttribute($chartDetail->dLbls->showCatName, 'val', 'string'); } if (isset($chartDetail->dLbls->showSerName)) { $plotAttributes['showSerName'] = self::getAttribute($chartDetail->dLbls->showSerName, 'val', 'string'); } if (isset($chartDetail->dLbls->showPercent)) { $plotAttributes['showPercent'] = self::getAttribute($chartDetail->dLbls->showPercent, 'val', 'string'); } if (isset($chartDetail->dLbls->showBubbleSize)) { $plotAttributes['showBubbleSize'] = self::getAttribute($chartDetail->dLbls->showBubbleSize, 'val', 'string'); } if (isset($chartDetail->dLbls->showLeaderLines)) { $plotAttributes['showLeaderLines'] = self::getAttribute($chartDetail->dLbls->showLeaderLines, 'val', 'string'); } if (isset($chartDetail->dLbls->spPr)) { $sppr = $chartDetail->dLbls->spPr->children($this->aNamespace); if (isset($sppr->solidFill)) { $plotAttributes['labelFillColor'] = new ChartColor($this->readColor($sppr->solidFill)); } if (isset($sppr->ln->solidFill)) { $plotAttributes['labelBorderColor'] = new ChartColor($this->readColor($sppr->ln->solidFill)); } } if (isset($chartDetail->dLbls->txPr)) { $txpr = $chartDetail->dLbls->txPr->children($this->aNamespace); if (isset($txpr->p)) { $plotAttributes['labelFont'] = $this->parseFont($txpr->p); if (isset($txpr->p->pPr->defRPr->effectLst)) { $labelEffects = new GridLines(); $this->readEffects($txpr->p->pPr->defRPr, $labelEffects, false); $plotAttributes['labelEffects'] = $labelEffects; } } } } return $plotAttributes; } /** * @param mixed $plotAttributes */ private function setChartAttributes(Layout $plotArea, $plotAttributes): void { foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) { switch ($plotAttributeKey) { case 'showLegendKey': $plotArea->setShowLegendKey($plotAttributeValue); break; case 'showVal': $plotArea->setShowVal($plotAttributeValue); break; case 'showCatName': $plotArea->setShowCatName($plotAttributeValue); break; case 'showSerName': $plotArea->setShowSerName($plotAttributeValue); break; case 'showPercent': $plotArea->setShowPercent($plotAttributeValue); break; case 'showBubbleSize': $plotArea->setShowBubbleSize($plotAttributeValue); break; case 'showLeaderLines': $plotArea->setShowLeaderLines($plotAttributeValue); break; } } } private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject, bool $getSppr = true): void { if (!isset($chartObject)) { return; } if ($getSppr) { if (!isset($chartDetail->spPr)) { return; } $sppr = $chartDetail->spPr->children($this->aNamespace); } else { $sppr = $chartDetail; } if (isset($sppr->effectLst->glow)) { $axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / ChartProperties::POINTS_WIDTH_MULTIPLIER; if ($axisGlowSize != 0.0) { $colorArray = $this->readColor($sppr->effectLst->glow); $chartObject->setGlowProperties($axisGlowSize, $colorArray['value'], $colorArray['alpha'], $colorArray['type']); } } if (isset($sppr->effectLst->softEdge)) { /** @var string */ $softEdgeSize = self::getAttribute($sppr->effectLst->softEdge, 'rad', 'string'); if (is_numeric($softEdgeSize)) { $chartObject->setSoftEdges((float) ChartProperties::xmlToPoints($softEdgeSize)); } } $type = ''; foreach (self::SHADOW_TYPES as $shadowType) { if (isset($sppr->effectLst->$shadowType)) { $type = $shadowType; break; } } if ($type !== '') { /** @var string */ $blur = self::getAttribute($sppr->effectLst->$type, 'blurRad', 'string'); $blur = is_numeric($blur) ? ChartProperties::xmlToPoints($blur) : null; /** @var string */ $dist = self::getAttribute($sppr->effectLst->$type, 'dist', 'string'); $dist = is_numeric($dist) ? ChartProperties::xmlToPoints($dist) : null; /** @var string */ $direction = self::getAttribute($sppr->effectLst->$type, 'dir', 'string'); $direction = is_numeric($direction) ? ChartProperties::xmlToAngle($direction) : null; $algn = self::getAttribute($sppr->effectLst->$type, 'algn', 'string'); $rot = self::getAttribute($sppr->effectLst->$type, 'rotWithShape', 'string'); $size = []; foreach (['sx', 'sy'] as $sizeType) { $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string'); if (is_numeric($sizeValue)) { $size[$sizeType] = ChartProperties::xmlToTenthOfPercent((string) $sizeValue); } else { $size[$sizeType] = null; } } foreach (['kx', 'ky'] as $sizeType) { $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string'); if (is_numeric($sizeValue)) { $size[$sizeType] = ChartProperties::xmlToAngle((string) $sizeValue); } else { $size[$sizeType] = null; } } $colorArray = $this->readColor($sppr->effectLst->$type); $chartObject ->setShadowProperty('effect', $type) ->setShadowProperty('blur', $blur) ->setShadowProperty('direction', $direction) ->setShadowProperty('distance', $dist) ->setShadowProperty('algn', $algn) ->setShadowProperty('rotWithShape', $rot) ->setShadowProperty('size', $size) ->setShadowProperty('color', $colorArray); } } private const SHADOW_TYPES = [ 'outerShdw', 'innerShdw', ]; private function readColor(SimpleXMLElement $colorXml): array { $result = [ 'type' => null, 'value' => null, 'alpha' => null, 'brightness' => null, ]; foreach (ChartColor::EXCEL_COLOR_TYPES as $type) { if (isset($colorXml->$type)) { $result['type'] = $type; $result['value'] = self::getAttribute($colorXml->$type, 'val', 'string'); if (isset($colorXml->$type->alpha)) { /** @var string */ $alpha = self::getAttribute($colorXml->$type->alpha, 'val', 'string'); if (is_numeric($alpha)) { $result['alpha'] = ChartColor::alphaFromXml($alpha); } } if (isset($colorXml->$type->lumMod)) { /** @var string */ $brightness = self::getAttribute($colorXml->$type->lumMod, 'val', 'string'); if (is_numeric($brightness)) { $result['brightness'] = ChartColor::alphaFromXml($brightness); } } break; } } return $result; } private function readLineStyle(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void { if (!isset($chartObject, $chartDetail->spPr)) { return; } $sppr = $chartDetail->spPr->children($this->aNamespace); if (!isset($sppr->ln)) { return; } $lineWidth = null; /** @var string */ $lineWidthTemp = self::getAttribute($sppr->ln, 'w', 'string'); if (is_numeric($lineWidthTemp)) { $lineWidth = ChartProperties::xmlToPoints($lineWidthTemp); } /** @var string */ $compoundType = self::getAttribute($sppr->ln, 'cmpd', 'string'); /** @var string */ $dashType = self::getAttribute($sppr->ln->prstDash, 'val', 'string'); /** @var string */ $capType = self::getAttribute($sppr->ln, 'cap', 'string'); if (isset($sppr->ln->miter)) { $joinType = ChartProperties::LINE_STYLE_JOIN_MITER; } elseif (isset($sppr->ln->bevel)) { $joinType = ChartProperties::LINE_STYLE_JOIN_BEVEL; } else { $joinType = ''; } $headArrowSize = ''; $endArrowSize = ''; /** @var string */ $headArrowType = self::getAttribute($sppr->ln->headEnd, 'type', 'string'); /** @var string */ $headArrowWidth = self::getAttribute($sppr->ln->headEnd, 'w', 'string'); /** @var string */ $headArrowLength = self::getAttribute($sppr->ln->headEnd, 'len', 'string'); /** @var string */ $endArrowType = self::getAttribute($sppr->ln->tailEnd, 'type', 'string'); /** @var string */ $endArrowWidth = self::getAttribute($sppr->ln->tailEnd, 'w', 'string'); /** @var string */ $endArrowLength = self::getAttribute($sppr->ln->tailEnd, 'len', 'string'); $chartObject->setLineStyleProperties( $lineWidth, $compoundType, $dashType, $capType, $joinType, $headArrowType, $headArrowSize, $endArrowType, $endArrowSize, $headArrowWidth, $headArrowLength, $endArrowWidth, $endArrowLength ); $colorArray = $this->readColor($sppr->ln->solidFill); $chartObject->getLineColor()->setColorPropertiesArray($colorArray); } private function setAxisProperties(SimpleXMLElement $chartDetail, ?Axis $whichAxis): void { if (!isset($whichAxis)) { return; } if (isset($chartDetail->delete)) { $whichAxis->setAxisOption('hidden', (string) self::getAttribute($chartDetail->delete, 'val', 'string')); } if (isset($chartDetail->numFmt)) { $whichAxis->setAxisNumberProperties( (string) self::getAttribute($chartDetail->numFmt, 'formatCode', 'string'), null, (int) self::getAttribute($chartDetail->numFmt, 'sourceLinked', 'int') ); } if (isset($chartDetail->crossBetween)) { $whichAxis->setCrossBetween((string) self::getAttribute($chartDetail->crossBetween, 'val', 'string')); } if (isset($chartDetail->majorTickMark)) { $whichAxis->setAxisOption('major_tick_mark', (string) self::getAttribute($chartDetail->majorTickMark, 'val', 'string')); } if (isset($chartDetail->minorTickMark)) { $whichAxis->setAxisOption('minor_tick_mark', (string) self::getAttribute($chartDetail->minorTickMark, 'val', 'string')); } if (isset($chartDetail->tickLblPos)) { $whichAxis->setAxisOption('axis_labels', (string) self::getAttribute($chartDetail->tickLblPos, 'val', 'string')); } if (isset($chartDetail->crosses)) { $whichAxis->setAxisOption('horizontal_crosses', (string) self::getAttribute($chartDetail->crosses, 'val', 'string')); } if (isset($chartDetail->crossesAt)) { $whichAxis->setAxisOption('horizontal_crosses_value', (string) self::getAttribute($chartDetail->crossesAt, 'val', 'string')); } if (isset($chartDetail->scaling->orientation)) { $whichAxis->setAxisOption('orientation', (string) self::getAttribute($chartDetail->scaling->orientation, 'val', 'string')); } if (isset($chartDetail->scaling->max)) { $whichAxis->setAxisOption('maximum', (string) self::getAttribute($chartDetail->scaling->max, 'val', 'string')); } if (isset($chartDetail->scaling->min)) { $whichAxis->setAxisOption('minimum', (string) self::getAttribute($chartDetail->scaling->min, 'val', 'string')); } if (isset($chartDetail->scaling->min)) { $whichAxis->setAxisOption('minimum', (string) self::getAttribute($chartDetail->scaling->min, 'val', 'string')); } if (isset($chartDetail->majorUnit)) { $whichAxis->setAxisOption('major_unit', (string) self::getAttribute($chartDetail->majorUnit, 'val', 'string')); } if (isset($chartDetail->minorUnit)) { $whichAxis->setAxisOption('minor_unit', (string) self::getAttribute($chartDetail->minorUnit, 'val', 'string')); } if (isset($chartDetail->baseTimeUnit)) { $whichAxis->setAxisOption('baseTimeUnit', (string) self::getAttribute($chartDetail->baseTimeUnit, 'val', 'string')); } if (isset($chartDetail->majorTimeUnit)) { $whichAxis->setAxisOption('majorTimeUnit', (string) self::getAttribute($chartDetail->majorTimeUnit, 'val', 'string')); } if (isset($chartDetail->minorTimeUnit)) { $whichAxis->setAxisOption('minorTimeUnit', (string) self::getAttribute($chartDetail->minorTimeUnit, 'val', 'string')); } if (isset($chartDetail->txPr)) { $children = $chartDetail->txPr->children($this->aNamespace); $addAxisText = false; $axisText = new AxisText(); if (isset($children->bodyPr)) { /** @var string */ $textRotation = self::getAttribute($children->bodyPr, 'rot', 'string'); if (is_numeric($textRotation)) { $axisText->setRotation((int) ChartProperties::xmlToAngle($textRotation)); $addAxisText = true; } } if (isset($children->p->pPr->defRPr)) { $font = $this->parseFont($children->p); if ($font !== null) { $axisText->setFont($font); $addAxisText = true; } } if (isset($children->p->pPr->defRPr->effectLst)) { $this->readEffects($children->p->pPr->defRPr, $axisText, false); $addAxisText = true; } if ($addAxisText) { $whichAxis->setAxisText($axisText); } } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php000064400000002635152427537250017226 0ustar00themeName = $themeName; $this->colourSchemeName = $colourSchemeName; $this->colourMap = $colourMap; } /** * Not called by Reader, never accessible any other time. * * @return string * * @codeCoverageIgnore */ public function getThemeName() { return $this->themeName; } /** * Not called by Reader, never accessible any other time. * * @return string * * @codeCoverageIgnore */ public function getColourSchemeName() { return $this->colourSchemeName; } /** * Get colour Map Value by Position. * * @param int $index * * @return null|string */ public function getColourByIndex($index) { return $this->colourMap[$index] ?? null; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php000064400000000676152427537250020723 0ustar00master = $master; $this->formula = $formula; } public function master(): string { return $this->master; } public function formula(): string { return $this->formula; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php000064400000015201152427537250020233 0ustar00parent = $parent; $this->worksheetXml = $worksheetXml; } public function load(): void { // Remove all "$" in the auto filter range $autoFilterRange = (string) preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref'] ?? ''); if (strpos($autoFilterRange, ':') !== false) { $this->readAutoFilter($autoFilterRange, $this->worksheetXml); } } private function readAutoFilter(string $autoFilterRange, SimpleXMLElement $xmlSheet): void { $autoFilter = $this->parent->getAutoFilter(); $autoFilter->setRange($autoFilterRange); foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) { $column = $autoFilter->getColumnByOffset((int) $filterColumn['colId']); // Check for standard filters if ($filterColumn->filters) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER); $filters = $filterColumn->filters; if ((isset($filters['blank'])) && ((int) $filters['blank'] == 1)) { // Operator is undefined, but always treated as EQUAL $column->createRule()->setRule('', '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); } // Standard filters are always an OR join, so no join rule needs to be set // Entries can be either filter elements foreach ($filters->filter as $filterRule) { // Operator is undefined, but always treated as EQUAL $column->createRule()->setRule('', (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); } // Or Date Group elements $this->readDateRangeAutoFilter($filters, $column); } // Check for custom filters $this->readCustomAutoFilter($filterColumn, $column); // Check for dynamic filters $this->readDynamicAutoFilter($filterColumn, $column); // Check for dynamic filters $this->readTopTenAutoFilter($filterColumn, $column); } $autoFilter->setEvaluated(true); } private function readDateRangeAutoFilter(SimpleXMLElement $filters, Column $column): void { foreach ($filters->dateGroupItem as $dateGroupItem) { // Operator is undefined, but always treated as EQUAL $column->createRule()->setRule( '', [ 'year' => (string) $dateGroupItem['year'], 'month' => (string) $dateGroupItem['month'], 'day' => (string) $dateGroupItem['day'], 'hour' => (string) $dateGroupItem['hour'], 'minute' => (string) $dateGroupItem['minute'], 'second' => (string) $dateGroupItem['second'], ], (string) $dateGroupItem['dateTimeGrouping'] )->setRuleType(Rule::AUTOFILTER_RULETYPE_DATEGROUP); } } private function readCustomAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void { if (isset($filterColumn, $filterColumn->customFilters)) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER); $customFilters = $filterColumn->customFilters; // Custom filters can an AND or an OR join; // and there should only ever be one or two entries if ((isset($customFilters['and'])) && ((string) $customFilters['and'] === '1')) { $column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND); } foreach ($customFilters->customFilter as $filterRule) { $column->createRule()->setRule( (string) $filterRule['operator'], (string) $filterRule['val'] )->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER); } } } private function readDynamicAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void { if (isset($filterColumn, $filterColumn->dynamicFilter)) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); // We should only ever have one dynamic filter foreach ($filterColumn->dynamicFilter as $filterRule) { // Operator is undefined, but always treated as EQUAL $column->createRule()->setRule( '', (string) $filterRule['val'], (string) $filterRule['type'] )->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); if (isset($filterRule['val'])) { $column->setAttribute('val', (string) $filterRule['val']); } if (isset($filterRule['maxVal'])) { $column->setAttribute('maxVal', (string) $filterRule['maxVal']); } } } } private function readTopTenAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void { if (isset($filterColumn, $filterColumn->top10)) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER); // We should only ever have one top10 filter foreach ($filterColumn->top10 as $filterRule) { $column->createRule()->setRule( ( ((isset($filterRule['percent'])) && ((string) $filterRule['percent'] === '1')) ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE ), (string) $filterRule['val'], ( ((isset($filterRule['top'])) && ((string) $filterRule['top'] === '1')) ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM ) )->setRuleType(Rule::AUTOFILTER_RULETYPE_TOPTENFILTER); } } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php000064400000013142152427537250021102 0ustar00pattern = $pattern; $this->disableEntityLoaderCheck(); // A fatal error will bypass the destructor, so we register a shutdown here if (!self::$shutdownRegistered) { self::$shutdownRegistered = true; register_shutdown_function([__CLASS__, 'shutdown']); } } public static function getInstance(Reader\IReader $reader): self { $pattern = ($reader instanceof Reader\Html) ? '= 1; case 1: return PHP_RELEASE_VERSION >= 13; case 0: return PHP_RELEASE_VERSION >= 27; } return true; } return false; } /** * @codeCoverageIgnore */ private function disableEntityLoaderCheck(): void { if (\PHP_VERSION_ID < 80000) { $libxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true); if (self::$libxmlDisableEntityLoaderValue === null) { self::$libxmlDisableEntityLoaderValue = $libxmlDisableEntityLoaderValue; } } } /** * @codeCoverageIgnore */ public static function shutdown(): void { if (self::$libxmlDisableEntityLoaderValue !== null && \PHP_VERSION_ID < 80000) { libxml_disable_entity_loader(self::$libxmlDisableEntityLoaderValue); self::$libxmlDisableEntityLoaderValue = null; } } public function __destruct() { self::shutdown(); } public function setAdditionalCallback(callable $callback): void { $this->callback = $callback; } /** @param mixed $arg */ private static function forceString($arg): string { return is_string($arg) ? $arg : ''; } /** * @param string $xml * * @return string */ private function toUtf8($xml) { $charset = $this->findCharSet($xml); $foundUtf7 = $charset === 'UTF-7'; if ($charset !== 'UTF-8') { $testStart = '/^.{0,4}\\s*disableEntityLoaderCheck(); // Don't rely purely on libxml_disable_entity_loader() $pattern = '/\\0*' . implode('\\0*', /** @scrutinizer ignore-type */ str_split($this->pattern)) . '\\0*/'; $xml = "$xml"; if (preg_match($pattern, $xml)) { throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); } $xml = $this->toUtf8($xml); if (preg_match($pattern, $xml)) { throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); } if ($this->callback !== null) { $xml = call_user_func($this->callback, $xml); } return $xml; } /** * Scan the XML for use of scan(file_get_contents($filestream)); } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php000064400000003452152427537250017643 0ustar00 '000000', 0x09 => 'FFFFFF', 0x0A => 'FF0000', 0x0B => '00FF00', 0x0C => '0000FF', 0x0D => 'FFFF00', 0x0E => 'FF00FF', 0x0F => '00FFFF', 0x10 => '800000', 0x11 => '008000', 0x12 => '000080', 0x13 => '808000', 0x14 => '800080', 0x15 => '008080', 0x16 => 'C0C0C0', 0x17 => '808080', 0x18 => '8080FF', 0x19 => '802060', 0x1A => 'FFFFC0', 0x1B => 'A0E0F0', 0x1C => '600080', 0x1D => 'FF8080', 0x1E => '0080C0', 0x1F => 'C0C0FF', 0x20 => '000080', 0x21 => 'FF00FF', 0x22 => 'FFFF00', 0x23 => '00FFFF', 0x24 => '800080', 0x25 => '800000', 0x26 => '008080', 0x27 => '0000FF', 0x28 => '00CFFF', 0x29 => '69FFFF', 0x2A => 'E0FFE0', 0x2B => 'FFFF80', 0x2C => 'A6CAF0', 0x2D => 'DD9CB3', 0x2E => 'B38FEE', 0x2F => 'E3E3E3', 0x30 => '2A6FF9', 0x31 => '3FB8CD', 0x32 => '488436', 0x33 => '958C41', 0x34 => '8E5E42', 0x35 => 'A0627A', 0x36 => '624FAC', 0x37 => '969696', 0x38 => '1D2FBE', 0x39 => '286676', 0x3A => '004500', 0x3B => '453E01', 0x3C => '6A2813', 0x3D => '85396A', 0x3E => '4A3285', 0x3F => '424242', ]; /** * Map color array from BIFF5 built-in color index. * * @param int $color * * @return array */ public static function lookup($color) { return ['rgb' => self::BIFF5_COLOR_MAP[$color] ?? '000000']; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php000064400000003452152427537250017646 0ustar00 '000000', 0x09 => 'FFFFFF', 0x0A => 'FF0000', 0x0B => '00FF00', 0x0C => '0000FF', 0x0D => 'FFFF00', 0x0E => 'FF00FF', 0x0F => '00FFFF', 0x10 => '800000', 0x11 => '008000', 0x12 => '000080', 0x13 => '808000', 0x14 => '800080', 0x15 => '008080', 0x16 => 'C0C0C0', 0x17 => '808080', 0x18 => '9999FF', 0x19 => '993366', 0x1A => 'FFFFCC', 0x1B => 'CCFFFF', 0x1C => '660066', 0x1D => 'FF8080', 0x1E => '0066CC', 0x1F => 'CCCCFF', 0x20 => '000080', 0x21 => 'FF00FF', 0x22 => 'FFFF00', 0x23 => '00FFFF', 0x24 => '800080', 0x25 => '800000', 0x26 => '008080', 0x27 => '0000FF', 0x28 => '00CCFF', 0x29 => 'CCFFFF', 0x2A => 'CCFFCC', 0x2B => 'FFFF99', 0x2C => '99CCFF', 0x2D => 'FF99CC', 0x2E => 'CC99FF', 0x2F => 'FFCC99', 0x30 => '3366FF', 0x31 => '33CCCC', 0x32 => '99CC00', 0x33 => 'FFCC00', 0x34 => 'FF9900', 0x35 => 'FF6600', 0x36 => '666699', 0x37 => '969696', 0x38 => '003366', 0x39 => '339966', 0x3A => '003300', 0x3B => '333300', 0x3C => '993300', 0x3D => '993366', 0x3E => '333399', 0x3F => '333333', ]; /** * Map color array from BIFF8 built-in color index. * * @param int $color * * @return array */ public static function lookup($color) { return ['rgb' => self::BIFF8_COLOR_MAP[$color] ?? '000000']; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php000064400000001300152427537250020404 0ustar00 '000000', 0x01 => 'FFFFFF', 0x02 => 'FF0000', 0x03 => '00FF00', 0x04 => '0000FF', 0x05 => 'FFFF00', 0x06 => 'FF00FF', 0x07 => '00FFFF', 0x40 => '000000', // system window text color 0x41 => 'FFFFFF', // system window background color ]; /** * Map built-in color to RGB value. * * @param int $color Indexed color * * @return array */ public static function lookup($color) { return ['rgb' => self::BUILTIN_COLOR_MAP[$color] ?? '000000']; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php000064400000002110152427537250020275 0ustar00 */ protected static $borderStyleMap = [ 0x00 => StyleBorder::BORDER_NONE, 0x01 => StyleBorder::BORDER_THIN, 0x02 => StyleBorder::BORDER_MEDIUM, 0x03 => StyleBorder::BORDER_DASHED, 0x04 => StyleBorder::BORDER_DOTTED, 0x05 => StyleBorder::BORDER_THICK, 0x06 => StyleBorder::BORDER_DOUBLE, 0x07 => StyleBorder::BORDER_HAIR, 0x08 => StyleBorder::BORDER_MEDIUMDASHED, 0x09 => StyleBorder::BORDER_DASHDOT, 0x0A => StyleBorder::BORDER_MEDIUMDASHDOT, 0x0B => StyleBorder::BORDER_DASHDOTDOT, 0x0C => StyleBorder::BORDER_MEDIUMDASHDOTDOT, 0x0D => StyleBorder::BORDER_SLANTDASHDOT, ]; public static function lookup(int $index): string { if (isset(self::$borderStyleMap[$index])) { return self::$borderStyleMap[$index]; } return StyleBorder::BORDER_NONE; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php000064400000002657152427537250021616 0ustar00 */ protected static $horizontalAlignmentMap = [ 0 => Alignment::HORIZONTAL_GENERAL, 1 => Alignment::HORIZONTAL_LEFT, 2 => Alignment::HORIZONTAL_CENTER, 3 => Alignment::HORIZONTAL_RIGHT, 4 => Alignment::HORIZONTAL_FILL, 5 => Alignment::HORIZONTAL_JUSTIFY, 6 => Alignment::HORIZONTAL_CENTER_CONTINUOUS, ]; /** * @var array */ protected static $verticalAlignmentMap = [ 0 => Alignment::VERTICAL_TOP, 1 => Alignment::VERTICAL_CENTER, 2 => Alignment::VERTICAL_BOTTOM, 3 => Alignment::VERTICAL_JUSTIFY, ]; public static function horizontal(Alignment $alignment, int $horizontal): void { if (array_key_exists($horizontal, self::$horizontalAlignmentMap)) { $alignment->setHorizontal(self::$horizontalAlignmentMap[$horizontal]); } } public static function vertical(Alignment $alignment, int $vertical): void { if (array_key_exists($vertical, self::$verticalAlignmentMap)) { $alignment->setVertical(self::$verticalAlignmentMap[$vertical]); } } public static function wrap(Alignment $alignment, int $wrap): void { $alignment->setWrapText((bool) $wrap); } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php000064400000002633152427537250021316 0ustar00 */ protected static $fillPatternMap = [ 0x00 => Fill::FILL_NONE, 0x01 => Fill::FILL_SOLID, 0x02 => Fill::FILL_PATTERN_MEDIUMGRAY, 0x03 => Fill::FILL_PATTERN_DARKGRAY, 0x04 => Fill::FILL_PATTERN_LIGHTGRAY, 0x05 => Fill::FILL_PATTERN_DARKHORIZONTAL, 0x06 => Fill::FILL_PATTERN_DARKVERTICAL, 0x07 => Fill::FILL_PATTERN_DARKDOWN, 0x08 => Fill::FILL_PATTERN_DARKUP, 0x09 => Fill::FILL_PATTERN_DARKGRID, 0x0A => Fill::FILL_PATTERN_DARKTRELLIS, 0x0B => Fill::FILL_PATTERN_LIGHTHORIZONTAL, 0x0C => Fill::FILL_PATTERN_LIGHTVERTICAL, 0x0D => Fill::FILL_PATTERN_LIGHTDOWN, 0x0E => Fill::FILL_PATTERN_LIGHTUP, 0x0F => Fill::FILL_PATTERN_LIGHTGRID, 0x10 => Fill::FILL_PATTERN_LIGHTTRELLIS, 0x11 => Fill::FILL_PATTERN_GRAY125, 0x12 => Fill::FILL_PATTERN_GRAY0625, ]; /** * Get fill pattern from index * OpenOffice documentation: 2.5.12. * * @param int $index * * @return string */ public static function lookup($index) { if (isset(self::$fillPatternMap[$index])) { return self::$fillPatternMap[$index]; } return Fill::FILL_NONE; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellFont.php000064400000001645152427537250020602 0ustar00setSuperscript(true); break; case 0x0002: $font->setSubscript(true); break; } } /** * @var array */ protected static $underlineMap = [ 0x01 => Font::UNDERLINE_SINGLE, 0x02 => Font::UNDERLINE_DOUBLE, 0x21 => Font::UNDERLINE_SINGLEACCOUNTING, 0x22 => Font::UNDERLINE_DOUBLEACCOUNTING, ]; public static function underline(Font $font, int $underline): void { if (array_key_exists($underline, self::$underlineMap)) { $font->setUnderline(self::$underlineMap[$underline]); } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php000064400000045726152427537250017215 0ustar00object = $object; } private const WHICH_ROUTINE = [ self::DGGCONTAINER => 'readDggContainer', self::DGG => 'readDgg', self::BSTORECONTAINER => 'readBstoreContainer', self::BSE => 'readBSE', self::BLIPJPEG => 'readBlipJPEG', self::BLIPPNG => 'readBlipPNG', self::OPT => 'readOPT', self::TERTIARYOPT => 'readTertiaryOPT', self::SPLITMENUCOLORS => 'readSplitMenuColors', self::DGCONTAINER => 'readDgContainer', self::DG => 'readDg', self::SPGRCONTAINER => 'readSpgrContainer', self::SPCONTAINER => 'readSpContainer', self::SPGR => 'readSpgr', self::SP => 'readSp', self::CLIENTTEXTBOX => 'readClientTextbox', self::CLIENTANCHOR => 'readClientAnchor', self::CLIENTDATA => 'readClientData', ]; /** * Load Escher stream data. May be a partial Escher stream. * * @param string $data * * @return BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer */ public function load($data) { $this->data = $data; // total byte size of Excel data (workbook global substream + sheet substreams) $this->dataSize = strlen($this->data); $this->pos = 0; // Parse Escher stream while ($this->pos < $this->dataSize) { // offset: 2; size: 2: Record Type $fbt = Xls::getUInt2d($this->data, $this->pos + 2); $routine = self::WHICH_ROUTINE[$fbt] ?? 'readDefault'; if (method_exists($this, $routine)) { $this->$routine(); } } return $this->object; } /** * Read a generic record. */ private function readDefault(): void { // offset 0; size: 2; recVer and recInstance //$verInstance = Xls::getUInt2d($this->data, $this->pos); // offset: 2; size: 2: Record Type //$fbt = Xls::getUInt2d($this->data, $this->pos + 2); // bit: 0-3; mask: 0x000F; recVer //$recVer = (0x000F & $verInstance) >> 0; $length = Xls::getInt4d($this->data, $this->pos + 4); //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; } /** * Read DggContainer record (Drawing Group Container). */ private function readDggContainer(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; // record is a container, read contents $dggContainer = new DggContainer(); $this->applyAttribute('setDggContainer', $dggContainer); $reader = new self($dggContainer); $reader->load($recordData); } /** * Read Dgg record (Drawing Group). */ private function readDgg(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; } /** * Read BstoreContainer record (Blip Store Container). */ private function readBstoreContainer(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; // record is a container, read contents $bstoreContainer = new BstoreContainer(); $this->applyAttribute('setBstoreContainer', $bstoreContainer); $reader = new self($bstoreContainer); $reader->load($recordData); } /** * Read BSE record. */ private function readBSE(): void { // offset: 0; size: 2; recVer and recInstance // bit: 4-15; mask: 0xFFF0; recInstance $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; // add BSE to BstoreContainer $BSE = new BSE(); $this->applyAttribute('addBSE', $BSE); $BSE->setBLIPType($recInstance); // offset: 0; size: 1; btWin32 (MSOBLIPTYPE) //$btWin32 = ord($recordData[0]); // offset: 1; size: 1; btWin32 (MSOBLIPTYPE) //$btMacOS = ord($recordData[1]); // offset: 2; size: 16; MD4 digest //$rgbUid = substr($recordData, 2, 16); // offset: 18; size: 2; tag //$tag = Xls::getUInt2d($recordData, 18); // offset: 20; size: 4; size of BLIP in bytes //$size = Xls::getInt4d($recordData, 20); // offset: 24; size: 4; number of references to this BLIP //$cRef = Xls::getInt4d($recordData, 24); // offset: 28; size: 4; MSOFO file offset //$foDelay = Xls::getInt4d($recordData, 28); // offset: 32; size: 1; unused1 //$unused1 = ord($recordData[32]); // offset: 33; size: 1; size of nameData in bytes (including null terminator) $cbName = ord($recordData[33]); // offset: 34; size: 1; unused2 //$unused2 = ord($recordData[34]); // offset: 35; size: 1; unused3 //$unused3 = ord($recordData[35]); // offset: 36; size: $cbName; nameData //$nameData = substr($recordData, 36, $cbName); // offset: 36 + $cbName, size: var; the BLIP data $blipData = substr($recordData, 36 + $cbName); // record is a container, read contents $reader = new self($BSE); $reader->load($blipData); } /** * Read BlipJPEG record. Holds raw JPEG image data. */ private function readBlipJPEG(): void { // offset: 0; size: 2; recVer and recInstance // bit: 4-15; mask: 0xFFF0; recInstance $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; $pos = 0; // offset: 0; size: 16; rgbUid1 (MD4 digest of) //$rgbUid1 = substr($recordData, 0, 16); $pos += 16; // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3 if (in_array($recInstance, [0x046B, 0x06E3])) { //$rgbUid2 = substr($recordData, 16, 16); $pos += 16; } // offset: var; size: 1; tag //$tag = ord($recordData[$pos]); ++$pos; // offset: var; size: var; the raw image data $data = substr($recordData, $pos); $blip = new Blip(); $blip->setData($data); $this->applyAttribute('setBlip', $blip); } /** * Read BlipPNG record. Holds raw PNG image data. */ private function readBlipPNG(): void { // offset: 0; size: 2; recVer and recInstance // bit: 4-15; mask: 0xFFF0; recInstance $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; $pos = 0; // offset: 0; size: 16; rgbUid1 (MD4 digest of) //$rgbUid1 = substr($recordData, 0, 16); $pos += 16; // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3 if ($recInstance == 0x06E1) { //$rgbUid2 = substr($recordData, 16, 16); $pos += 16; } // offset: var; size: 1; tag //$tag = ord($recordData[$pos]); ++$pos; // offset: var; size: var; the raw image data $data = substr($recordData, $pos); $blip = new Blip(); $blip->setData($data); $this->applyAttribute('setBlip', $blip); } /** * Read OPT record. This record may occur within DggContainer record or SpContainer. */ private function readOPT(): void { // offset: 0; size: 2; recVer and recInstance // bit: 4-15; mask: 0xFFF0; recInstance $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; $this->readOfficeArtRGFOPTE($recordData, $recInstance); } /** * Read TertiaryOPT record. */ private function readTertiaryOPT(): void { // offset: 0; size: 2; recVer and recInstance // bit: 4-15; mask: 0xFFF0; recInstance //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; $length = Xls::getInt4d($this->data, $this->pos + 4); //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; } /** * Read SplitMenuColors record. */ private function readSplitMenuColors(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; } /** * Read DgContainer record (Drawing Container). */ private function readDgContainer(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; // record is a container, read contents $dgContainer = new DgContainer(); $this->applyAttribute('setDgContainer', $dgContainer); $reader = new self($dgContainer); $reader->load($recordData); } /** * Read Dg record (Drawing). */ private function readDg(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; } /** * Read SpgrContainer record (Shape Group Container). */ private function readSpgrContainer(): void { // context is either context DgContainer or SpgrContainer $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; // record is a container, read contents $spgrContainer = new SpgrContainer(); if ($this->object instanceof DgContainer) { // DgContainer $this->object->setSpgrContainer($spgrContainer); } elseif ($this->object instanceof SpgrContainer) { // SpgrContainer $this->object->addChild($spgrContainer); } $reader = new self($spgrContainer); $reader->load($recordData); } /** * Read SpContainer record (Shape Container). */ private function readSpContainer(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); // add spContainer to spgrContainer $spContainer = new SpContainer(); $this->applyAttribute('addChild', $spContainer); // move stream pointer to next record $this->pos += 8 + $length; // record is a container, read contents $reader = new self($spContainer); $reader->load($recordData); } /** * Read Spgr record (Shape Group). */ private function readSpgr(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; } /** * Read Sp record (Shape). */ private function readSp(): void { // offset: 0; size: 2; recVer and recInstance // bit: 4-15; mask: 0xFFF0; recInstance //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; $length = Xls::getInt4d($this->data, $this->pos + 4); //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; } /** * Read ClientTextbox record. */ private function readClientTextbox(): void { // offset: 0; size: 2; recVer and recInstance // bit: 4-15; mask: 0xFFF0; recInstance //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; $length = Xls::getInt4d($this->data, $this->pos + 4); //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; } /** * Read ClientAnchor record. This record holds information about where the shape is anchored in worksheet. */ private function readClientAnchor(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; // offset: 2; size: 2; upper-left corner column index (0-based) $c1 = Xls::getUInt2d($recordData, 2); // offset: 4; size: 2; upper-left corner horizontal offset in 1/1024 of column width $startOffsetX = Xls::getUInt2d($recordData, 4); // offset: 6; size: 2; upper-left corner row index (0-based) $r1 = Xls::getUInt2d($recordData, 6); // offset: 8; size: 2; upper-left corner vertical offset in 1/256 of row height $startOffsetY = Xls::getUInt2d($recordData, 8); // offset: 10; size: 2; bottom-right corner column index (0-based) $c2 = Xls::getUInt2d($recordData, 10); // offset: 12; size: 2; bottom-right corner horizontal offset in 1/1024 of column width $endOffsetX = Xls::getUInt2d($recordData, 12); // offset: 14; size: 2; bottom-right corner row index (0-based) $r2 = Xls::getUInt2d($recordData, 14); // offset: 16; size: 2; bottom-right corner vertical offset in 1/256 of row height $endOffsetY = Xls::getUInt2d($recordData, 16); $this->applyAttribute('setStartCoordinates', Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1)); $this->applyAttribute('setStartOffsetX', $startOffsetX); $this->applyAttribute('setStartOffsetY', $startOffsetY); $this->applyAttribute('setEndCoordinates', Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1)); $this->applyAttribute('setEndOffsetX', $endOffsetX); $this->applyAttribute('setEndOffsetY', $endOffsetY); } /** * @param mixed $value */ private function applyAttribute(string $name, $value): void { if (method_exists($this->object, $name)) { $this->object->$name($value); } } /** * Read ClientData record. */ private function readClientData(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; } /** * Read OfficeArtRGFOPTE table of property-value pairs. * * @param string $data Binary data * @param int $n Number of properties */ private function readOfficeArtRGFOPTE($data, $n): void { $splicedComplexData = substr($data, 6 * $n); // loop through property-value pairs for ($i = 0; $i < $n; ++$i) { // read 6 bytes at a time $fopte = substr($data, 6 * $i, 6); // offset: 0; size: 2; opid $opid = Xls::getUInt2d($fopte, 0); // bit: 0-13; mask: 0x3FFF; opid.opid $opidOpid = (0x3FFF & $opid) >> 0; // bit: 14; mask 0x4000; 1 = value in op field is BLIP identifier //$opidFBid = (0x4000 & $opid) >> 14; // bit: 15; mask 0x8000; 1 = this is a complex property, op field specifies size of complex data $opidFComplex = (0x8000 & $opid) >> 15; // offset: 2; size: 4; the value for this property $op = Xls::getInt4d($fopte, 2); if ($opidFComplex) { $complexData = substr($splicedComplexData, 0, $op); $splicedComplexData = substr($splicedComplexData, $op); // we store string value with complex data $value = $complexData; } else { // we store integer value $value = $op; } if (method_exists($this->object, 'setOPT')) { $this->object->setOPT($opidOpid, $value); } } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php000064400000001013152427537250017645 0ustar00 '#NULL!', 0x07 => '#DIV/0!', 0x0F => '#VALUE!', 0x17 => '#REF!', 0x1D => '#NAME?', 0x24 => '#NUM!', 0x2A => '#N/A', ]; /** * Map error code, e.g. '#N/A'. * * @param int $code * * @return bool|string */ public static function lookup($code) { return self::ERROR_CODE_MAP[$code] ?? false; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php000064400000003602152427537250022013 0ustar00 */ private static $types = [ 0x00 => DataValidation::TYPE_NONE, 0x01 => DataValidation::TYPE_WHOLE, 0x02 => DataValidation::TYPE_DECIMAL, 0x03 => DataValidation::TYPE_LIST, 0x04 => DataValidation::TYPE_DATE, 0x05 => DataValidation::TYPE_TIME, 0x06 => DataValidation::TYPE_TEXTLENGTH, 0x07 => DataValidation::TYPE_CUSTOM, ]; /** * @var array */ private static $errorStyles = [ 0x00 => DataValidation::STYLE_STOP, 0x01 => DataValidation::STYLE_WARNING, 0x02 => DataValidation::STYLE_INFORMATION, ]; /** * @var array */ private static $operators = [ 0x00 => DataValidation::OPERATOR_BETWEEN, 0x01 => DataValidation::OPERATOR_NOTBETWEEN, 0x02 => DataValidation::OPERATOR_EQUAL, 0x03 => DataValidation::OPERATOR_NOTEQUAL, 0x04 => DataValidation::OPERATOR_GREATERTHAN, 0x05 => DataValidation::OPERATOR_LESSTHAN, 0x06 => DataValidation::OPERATOR_GREATERTHANOREQUAL, 0x07 => DataValidation::OPERATOR_LESSTHANOREQUAL, ]; public static function type(int $type): ?string { if (isset(self::$types[$type])) { return self::$types[$type]; } return null; } public static function errorStyle(int $errorStyle): ?string { if (isset(self::$errorStyles[$errorStyle])) { return self::$errorStyles[$errorStyle]; } return null; } public static function operator(int $operator): ?string { if (isset(self::$operators[$operator])) { return self::$operators[$operator]; } return null; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php000064400000002323152427537250022264 0ustar00 */ private static $types = [ 0x01 => Conditional::CONDITION_CELLIS, 0x02 => Conditional::CONDITION_EXPRESSION, ]; /** * @var array */ private static $operators = [ 0x00 => Conditional::OPERATOR_NONE, 0x01 => Conditional::OPERATOR_BETWEEN, 0x02 => Conditional::OPERATOR_NOTBETWEEN, 0x03 => Conditional::OPERATOR_EQUAL, 0x04 => Conditional::OPERATOR_NOTEQUAL, 0x05 => Conditional::OPERATOR_GREATERTHAN, 0x06 => Conditional::OPERATOR_LESSTHAN, 0x07 => Conditional::OPERATOR_GREATERTHANOREQUAL, 0x08 => Conditional::OPERATOR_LESSTHANOREQUAL, ]; public static function type(int $type): ?string { if (isset(self::$types[$type])) { return self::$types[$type]; } return null; } public static function operator(int $operator): ?string { if (isset(self::$operators[$operator])) { return self::$operators[$operator]; } return null; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php000064400000016665152427537250016371 0ustar00reset(); } /** * Reset the MD5 stream context. */ public function reset(): void { $this->a = 0x67452301; $this->b = self::signedInt(0xEFCDAB89); $this->c = self::signedInt(0x98BADCFE); $this->d = 0x10325476; } /** * Get MD5 stream context. * * @return string */ public function getContext() { $s = ''; foreach (['a', 'b', 'c', 'd'] as $i) { $v = $this->{$i}; $s .= chr($v & 0xff); $s .= chr(($v >> 8) & 0xff); $s .= chr(($v >> 16) & 0xff); $s .= chr(($v >> 24) & 0xff); } return $s; } /** * Add data to context. * * @param string $data Data to add */ public function add(string $data): void { // @phpstan-ignore-next-line $words = array_values(unpack('V16', $data)); $A = $this->a; $B = $this->b; $C = $this->c; $D = $this->d; $F = [self::class, 'f']; $G = [self::class, 'g']; $H = [self::class, 'h']; $I = [self::class, 'i']; // ROUND 1 self::step($F, $A, $B, $C, $D, $words[0], 7, 0xd76aa478); self::step($F, $D, $A, $B, $C, $words[1], 12, 0xe8c7b756); self::step($F, $C, $D, $A, $B, $words[2], 17, 0x242070db); self::step($F, $B, $C, $D, $A, $words[3], 22, 0xc1bdceee); self::step($F, $A, $B, $C, $D, $words[4], 7, 0xf57c0faf); self::step($F, $D, $A, $B, $C, $words[5], 12, 0x4787c62a); self::step($F, $C, $D, $A, $B, $words[6], 17, 0xa8304613); self::step($F, $B, $C, $D, $A, $words[7], 22, 0xfd469501); self::step($F, $A, $B, $C, $D, $words[8], 7, 0x698098d8); self::step($F, $D, $A, $B, $C, $words[9], 12, 0x8b44f7af); self::step($F, $C, $D, $A, $B, $words[10], 17, 0xffff5bb1); self::step($F, $B, $C, $D, $A, $words[11], 22, 0x895cd7be); self::step($F, $A, $B, $C, $D, $words[12], 7, 0x6b901122); self::step($F, $D, $A, $B, $C, $words[13], 12, 0xfd987193); self::step($F, $C, $D, $A, $B, $words[14], 17, 0xa679438e); self::step($F, $B, $C, $D, $A, $words[15], 22, 0x49b40821); // ROUND 2 self::step($G, $A, $B, $C, $D, $words[1], 5, 0xf61e2562); self::step($G, $D, $A, $B, $C, $words[6], 9, 0xc040b340); self::step($G, $C, $D, $A, $B, $words[11], 14, 0x265e5a51); self::step($G, $B, $C, $D, $A, $words[0], 20, 0xe9b6c7aa); self::step($G, $A, $B, $C, $D, $words[5], 5, 0xd62f105d); self::step($G, $D, $A, $B, $C, $words[10], 9, 0x02441453); self::step($G, $C, $D, $A, $B, $words[15], 14, 0xd8a1e681); self::step($G, $B, $C, $D, $A, $words[4], 20, 0xe7d3fbc8); self::step($G, $A, $B, $C, $D, $words[9], 5, 0x21e1cde6); self::step($G, $D, $A, $B, $C, $words[14], 9, 0xc33707d6); self::step($G, $C, $D, $A, $B, $words[3], 14, 0xf4d50d87); self::step($G, $B, $C, $D, $A, $words[8], 20, 0x455a14ed); self::step($G, $A, $B, $C, $D, $words[13], 5, 0xa9e3e905); self::step($G, $D, $A, $B, $C, $words[2], 9, 0xfcefa3f8); self::step($G, $C, $D, $A, $B, $words[7], 14, 0x676f02d9); self::step($G, $B, $C, $D, $A, $words[12], 20, 0x8d2a4c8a); // ROUND 3 self::step($H, $A, $B, $C, $D, $words[5], 4, 0xfffa3942); self::step($H, $D, $A, $B, $C, $words[8], 11, 0x8771f681); self::step($H, $C, $D, $A, $B, $words[11], 16, 0x6d9d6122); self::step($H, $B, $C, $D, $A, $words[14], 23, 0xfde5380c); self::step($H, $A, $B, $C, $D, $words[1], 4, 0xa4beea44); self::step($H, $D, $A, $B, $C, $words[4], 11, 0x4bdecfa9); self::step($H, $C, $D, $A, $B, $words[7], 16, 0xf6bb4b60); self::step($H, $B, $C, $D, $A, $words[10], 23, 0xbebfbc70); self::step($H, $A, $B, $C, $D, $words[13], 4, 0x289b7ec6); self::step($H, $D, $A, $B, $C, $words[0], 11, 0xeaa127fa); self::step($H, $C, $D, $A, $B, $words[3], 16, 0xd4ef3085); self::step($H, $B, $C, $D, $A, $words[6], 23, 0x04881d05); self::step($H, $A, $B, $C, $D, $words[9], 4, 0xd9d4d039); self::step($H, $D, $A, $B, $C, $words[12], 11, 0xe6db99e5); self::step($H, $C, $D, $A, $B, $words[15], 16, 0x1fa27cf8); self::step($H, $B, $C, $D, $A, $words[2], 23, 0xc4ac5665); // ROUND 4 self::step($I, $A, $B, $C, $D, $words[0], 6, 0xf4292244); self::step($I, $D, $A, $B, $C, $words[7], 10, 0x432aff97); self::step($I, $C, $D, $A, $B, $words[14], 15, 0xab9423a7); self::step($I, $B, $C, $D, $A, $words[5], 21, 0xfc93a039); self::step($I, $A, $B, $C, $D, $words[12], 6, 0x655b59c3); self::step($I, $D, $A, $B, $C, $words[3], 10, 0x8f0ccc92); self::step($I, $C, $D, $A, $B, $words[10], 15, 0xffeff47d); self::step($I, $B, $C, $D, $A, $words[1], 21, 0x85845dd1); self::step($I, $A, $B, $C, $D, $words[8], 6, 0x6fa87e4f); self::step($I, $D, $A, $B, $C, $words[15], 10, 0xfe2ce6e0); self::step($I, $C, $D, $A, $B, $words[6], 15, 0xa3014314); self::step($I, $B, $C, $D, $A, $words[13], 21, 0x4e0811a1); self::step($I, $A, $B, $C, $D, $words[4], 6, 0xf7537e82); self::step($I, $D, $A, $B, $C, $words[11], 10, 0xbd3af235); self::step($I, $C, $D, $A, $B, $words[2], 15, 0x2ad7d2bb); self::step($I, $B, $C, $D, $A, $words[9], 21, 0xeb86d391); $this->a = ($this->a + $A) & self::$allOneBits; $this->b = ($this->b + $B) & self::$allOneBits; $this->c = ($this->c + $C) & self::$allOneBits; $this->d = ($this->d + $D) & self::$allOneBits; } private static function f(int $X, int $Y, int $Z): int { return ($X & $Y) | ((~$X) & $Z); // X AND Y OR NOT X AND Z } private static function g(int $X, int $Y, int $Z): int { return ($X & $Z) | ($Y & (~$Z)); // X AND Z OR Y AND NOT Z } private static function h(int $X, int $Y, int $Z): int { return $X ^ $Y ^ $Z; // X XOR Y XOR Z } private static function i(int $X, int $Y, int $Z): int { return $Y ^ ($X | (~$Z)); // Y XOR (X OR NOT Z) } /** @param float|int $t may be float on 32-bit system */ private static function step(callable $func, int &$A, int $B, int $C, int $D, int $M, int $s, $t): void { $t = self::signedInt($t); $A = (int) ($A + call_user_func($func, $B, $C, $D) + $M + $t) & self::$allOneBits; $A = self::rotate($A, $s); $A = (int) ($B + $A) & self::$allOneBits; } /** @param float|int $result may be float on 32-bit system */ private static function signedInt($result): int { return is_int($result) ? $result : (int) (PHP_INT_MIN + $result - 1 - PHP_INT_MAX); } private static function rotate(int $decimal, int $bits): int { $binary = str_pad(decbin($decimal), 32, '0', STR_PAD_LEFT); return self::signedInt(bindec(substr($binary, $bits) . substr($binary, 0, $bits))); } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php000064400000001616152427537250017050 0ustar00 'FF0000'] */ public static function map($color, $palette, $version) { if ($color <= 0x07 || $color >= 0x40) { // special built-in color return Color\BuiltIn::lookup($color); } elseif (isset($palette[$color - 8])) { // palette color, color index 0x08 maps to pallete index 0 return $palette[$color - 8]; } // default color table if ($version == Xls::XLS_BIFF8) { return Color\BIFF8::lookup($color); } // BIFF5 return Color\BIFF5::lookup($color); } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php000064400000002774152427537250016370 0ustar00i = 0; $this->i < 256; ++$this->i) { $this->s[$this->i] = $this->i; } $this->j = 0; for ($this->i = 0; $this->i < 256; ++$this->i) { $this->j = ($this->j + $this->s[$this->i] + ord($key[$this->i % $len])) % 256; $t = $this->s[$this->i]; $this->s[$this->i] = $this->s[$this->j]; $this->s[$this->j] = $t; } $this->i = $this->j = 0; } /** * Symmetric decryption/encryption function. * * @param string $data Data to encrypt/decrypt * * @return string */ public function RC4($data) { $len = strlen($data); for ($c = 0; $c < $len; ++$c) { $this->i = ($this->i + 1) % 256; $this->j = ($this->j + $this->s[$this->i]) % 256; $t = $this->s[$this->i]; $this->s[$this->i] = $this->s[$this->j]; $this->s[$this->j] = $t; $t = ($this->s[$this->i] + $this->s[$this->j]) % 256; $data[$c] = chr(ord($data[$c]) ^ $this->s[$t]); } return $data; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php000064400000005207152427537250017752 0ustar00 [ 'solid' => FillStyles::FILL_SOLID, 'gray75' => FillStyles::FILL_PATTERN_DARKGRAY, 'gray50' => FillStyles::FILL_PATTERN_MEDIUMGRAY, 'gray25' => FillStyles::FILL_PATTERN_LIGHTGRAY, 'gray125' => FillStyles::FILL_PATTERN_GRAY125, 'gray0625' => FillStyles::FILL_PATTERN_GRAY0625, 'horzstripe' => FillStyles::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe 'vertstripe' => FillStyles::FILL_PATTERN_DARKVERTICAL, // vertical stripe 'reversediagstripe' => FillStyles::FILL_PATTERN_DARKUP, // reverse diagonal stripe 'diagstripe' => FillStyles::FILL_PATTERN_DARKDOWN, // diagonal stripe 'diagcross' => FillStyles::FILL_PATTERN_DARKGRID, // diagoanl crosshatch 'thickdiagcross' => FillStyles::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch 'thinhorzstripe' => FillStyles::FILL_PATTERN_LIGHTHORIZONTAL, 'thinvertstripe' => FillStyles::FILL_PATTERN_LIGHTVERTICAL, 'thinreversediagstripe' => FillStyles::FILL_PATTERN_LIGHTUP, 'thindiagstripe' => FillStyles::FILL_PATTERN_LIGHTDOWN, 'thinhorzcross' => FillStyles::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch 'thindiagcross' => FillStyles::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch ], ]; public function parseStyle(SimpleXMLElement $styleAttributes): array { $style = []; foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValuex) { $styleAttributeValue = (string) $styleAttributeValuex; switch ($styleAttributeKey) { case 'Color': $style['fill']['endColor']['rgb'] = substr($styleAttributeValue, 1); $style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1); break; case 'PatternColor': $style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1); break; case 'Pattern': $lcStyleAttributeValue = strtolower((string) $styleAttributeValue); $style['fill']['fillType'] = self::FILL_MAPPINGS['fillType'][$lcStyleAttributeValue] ?? FillStyles::FILL_NONE; break; } } return $style; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php000064400000007235152427537250020304 0ustar00 [ '1continuous' => BorderStyle::BORDER_THIN, '1dash' => BorderStyle::BORDER_DASHED, '1dashdot' => BorderStyle::BORDER_DASHDOT, '1dashdotdot' => BorderStyle::BORDER_DASHDOTDOT, '1dot' => BorderStyle::BORDER_DOTTED, '1double' => BorderStyle::BORDER_DOUBLE, '2continuous' => BorderStyle::BORDER_MEDIUM, '2dash' => BorderStyle::BORDER_MEDIUMDASHED, '2dashdot' => BorderStyle::BORDER_MEDIUMDASHDOT, '2dashdotdot' => BorderStyle::BORDER_MEDIUMDASHDOTDOT, '2dot' => BorderStyle::BORDER_DOTTED, '2double' => BorderStyle::BORDER_DOUBLE, '3continuous' => BorderStyle::BORDER_THICK, '3dash' => BorderStyle::BORDER_MEDIUMDASHED, '3dashdot' => BorderStyle::BORDER_MEDIUMDASHDOT, '3dashdotdot' => BorderStyle::BORDER_MEDIUMDASHDOTDOT, '3dot' => BorderStyle::BORDER_DOTTED, '3double' => BorderStyle::BORDER_DOUBLE, ], ]; public function parseStyle(SimpleXMLElement $styleData, array $namespaces): array { $style = []; $diagonalDirection = ''; $borderPosition = ''; foreach ($styleData->Border as $borderStyle) { $borderAttributes = self::getAttributes($borderStyle, $namespaces['ss']); $thisBorder = []; $styleType = (string) $borderAttributes->Weight; $styleType .= strtolower((string) $borderAttributes->LineStyle); $thisBorder['borderStyle'] = self::BORDER_MAPPINGS['borderStyle'][$styleType] ?? BorderStyle::BORDER_NONE; foreach ($borderAttributes as $borderStyleKey => $borderStyleValuex) { $borderStyleValue = (string) $borderStyleValuex; switch ($borderStyleKey) { case 'Position': [$borderPosition, $diagonalDirection] = $this->parsePosition($borderStyleValue, $diagonalDirection); break; case 'Color': $borderColour = substr($borderStyleValue, 1); $thisBorder['color']['rgb'] = $borderColour; break; } } if ($borderPosition) { $style['borders'][$borderPosition] = $thisBorder; } elseif ($diagonalDirection) { $style['borders']['diagonalDirection'] = $diagonalDirection; $style['borders']['diagonal'] = $thisBorder; } } return $style; } protected function parsePosition(string $borderStyleValue, string $diagonalDirection): array { $borderStyleValue = strtolower($borderStyleValue); if (in_array($borderStyleValue, self::BORDER_POSITIONS)) { $borderPosition = $borderStyleValue; } elseif ($borderStyleValue === 'diagonalleft') { $diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_DOWN; } elseif ($borderStyleValue === 'diagonalright') { $diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_UP; } return [$borderPosition ?? null, $diagonalDirection]; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php000064400000003563152427537250021005 0ustar00 $styleAttributeValue) { $styleAttributeValue = (string) $styleAttributeValue; switch ($styleAttributeKey) { case 'Vertical': if (self::identifyFixedStyleValue(self::VERTICAL_ALIGNMENT_STYLES, $styleAttributeValue)) { $style['alignment']['vertical'] = $styleAttributeValue; } break; case 'Horizontal': if (self::identifyFixedStyleValue(self::HORIZONTAL_ALIGNMENT_STYLES, $styleAttributeValue)) { $style['alignment']['horizontal'] = $styleAttributeValue; } break; case 'WrapText': $style['alignment']['wrapText'] = true; break; case 'Rotate': $style['alignment']['textRotation'] = $styleAttributeValue; break; } } return $style; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php000064400000004532152427537250017772 0ustar00 $styleAttributeValue) { $styleAttributeValue = (string) $styleAttributeValue; switch ($styleAttributeKey) { case 'FontName': $style['font']['name'] = $styleAttributeValue; break; case 'Size': $style['font']['size'] = $styleAttributeValue; break; case 'Color': $style['font']['color']['rgb'] = substr($styleAttributeValue, 1); break; case 'Bold': $style['font']['bold'] = $styleAttributeValue === '1'; break; case 'Italic': $style['font']['italic'] = $styleAttributeValue === '1'; break; case 'Underline': $style = $this->parseUnderline($style, $styleAttributeValue); break; case 'VerticalAlign': $style = $this->parseVerticalAlign($style, $styleAttributeValue); break; } } return $style; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php000064400000001500152427537250021455 0ustar00 $styleAttributeValue) { $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue); switch ($styleAttributeValue) { case 'Short Date': $styleAttributeValue = 'dd/mm/yyyy'; break; } if ($styleAttributeValue > '') { $style['numberFormat']['formatCode'] = $styleAttributeValue; } } return $style; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php000064400000001525152427537250020756 0ustar00') : ($simple->attributes($node) ?? new SimpleXMLElement('')); } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php000064400000011556152427537250020124 0ustar00spreadsheet = $spreadsheet; } public function readProperties(SimpleXMLElement $xml, array $namespaces): void { $this->readStandardProperties($xml); $this->readCustomProperties($xml, $namespaces); } protected function readStandardProperties(SimpleXMLElement $xml): void { if (isset($xml->DocumentProperties[0])) { $docProps = $this->spreadsheet->getProperties(); foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) { $propertyValue = (string) $propertyValue; $this->processStandardProperty($docProps, $propertyName, $propertyValue); } } } protected function readCustomProperties(SimpleXMLElement $xml, array $namespaces): void { if (isset($xml->CustomDocumentProperties) && is_iterable($xml->CustomDocumentProperties[0])) { $docProps = $this->spreadsheet->getProperties(); foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) { $propertyAttributes = self::getAttributes($propertyValue, $namespaces['dt']); $propertyName = (string) preg_replace_callback('/_x([0-9a-f]{4})_/i', [$this, 'hex2str'], $propertyName); $this->processCustomProperty($docProps, $propertyName, $propertyValue, $propertyAttributes); } } } protected function processStandardProperty( DocumentProperties $docProps, string $propertyName, string $stringValue ): void { switch ($propertyName) { case 'Title': $docProps->setTitle($stringValue); break; case 'Subject': $docProps->setSubject($stringValue); break; case 'Author': $docProps->setCreator($stringValue); break; case 'Created': $docProps->setCreated($stringValue); break; case 'LastAuthor': $docProps->setLastModifiedBy($stringValue); break; case 'LastSaved': $docProps->setModified($stringValue); break; case 'Company': $docProps->setCompany($stringValue); break; case 'Category': $docProps->setCategory($stringValue); break; case 'Manager': $docProps->setManager($stringValue); break; case 'HyperlinkBase': $docProps->setHyperlinkBase($stringValue); break; case 'Keywords': $docProps->setKeywords($stringValue); break; case 'Description': $docProps->setDescription($stringValue); break; } } protected function processCustomProperty( DocumentProperties $docProps, string $propertyName, ?SimpleXMLElement $propertyValue, SimpleXMLElement $propertyAttributes ): void { switch ((string) $propertyAttributes) { case 'boolean': $propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN; $propertyValue = (bool) (string) $propertyValue; break; case 'integer': $propertyType = DocumentProperties::PROPERTY_TYPE_INTEGER; $propertyValue = (int) $propertyValue; break; case 'float': $propertyType = DocumentProperties::PROPERTY_TYPE_FLOAT; $propertyValue = (float) $propertyValue; break; case 'dateTime.tz': case 'dateTime.iso8601tz': $propertyType = DocumentProperties::PROPERTY_TYPE_DATE; $propertyValue = trim((string) $propertyValue); break; default: $propertyType = DocumentProperties::PROPERTY_TYPE_STRING; $propertyValue = trim((string) $propertyValue); break; } $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType); } protected function hex2str(array $hex): string { return mb_chr((int) hexdec($hex[1]), 'UTF-8'); } private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement { return ($simple === null) ? new SimpleXMLElement('') : ($simple->attributes($node) ?? new SimpleXMLElement('')); } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php000064400000012235152427537250020360 0ustar00pageSetup($xmlX, $this->getPrintDefaults()); $this->printSettings = $this->printSetup($xmlX, $printSettings); } public function loadPageSettings(Spreadsheet $spreadsheet): void { $spreadsheet->getActiveSheet()->getPageSetup() ->setPaperSize($this->printSettings->paperSize) ->setOrientation($this->printSettings->orientation) ->setScale($this->printSettings->scale) ->setVerticalCentered($this->printSettings->verticalCentered) ->setHorizontalCentered($this->printSettings->horizontalCentered) ->setPageOrder($this->printSettings->printOrder); $spreadsheet->getActiveSheet()->getPageMargins() ->setTop($this->printSettings->topMargin) ->setHeader($this->printSettings->headerMargin) ->setLeft($this->printSettings->leftMargin) ->setRight($this->printSettings->rightMargin) ->setBottom($this->printSettings->bottomMargin) ->setFooter($this->printSettings->footerMargin); } private function getPrintDefaults(): stdClass { return (object) [ 'paperSize' => 9, 'orientation' => PageSetup::ORIENTATION_DEFAULT, 'scale' => 100, 'horizontalCentered' => false, 'verticalCentered' => false, 'printOrder' => PageSetup::PAGEORDER_DOWN_THEN_OVER, 'topMargin' => 0.75, 'headerMargin' => 0.3, 'leftMargin' => 0.7, 'rightMargin' => 0.7, 'bottomMargin' => 0.75, 'footerMargin' => 0.3, ]; } private function pageSetup(SimpleXMLElement $xmlX, stdClass $printDefaults): stdClass { if (isset($xmlX->WorksheetOptions->PageSetup)) { foreach ($xmlX->WorksheetOptions->PageSetup as $pageSetupData) { foreach ($pageSetupData as $pageSetupKey => $pageSetupValue) { /** @scrutinizer ignore-call */ $pageSetupAttributes = $pageSetupValue->attributes(Namespaces::URN_EXCEL); if ($pageSetupAttributes !== null) { switch ($pageSetupKey) { case 'Layout': $this->setLayout($printDefaults, $pageSetupAttributes); break; case 'Header': $printDefaults->headerMargin = (float) $pageSetupAttributes->Margin ?: 1.0; break; case 'Footer': $printDefaults->footerMargin = (float) $pageSetupAttributes->Margin ?: 1.0; break; case 'PageMargins': $this->setMargins($printDefaults, $pageSetupAttributes); break; } } } } } return $printDefaults; } private function printSetup(SimpleXMLElement $xmlX, stdClass $printDefaults): stdClass { if (isset($xmlX->WorksheetOptions->Print)) { foreach ($xmlX->WorksheetOptions->Print as $printData) { foreach ($printData as $printKey => $printValue) { switch ($printKey) { case 'LeftToRight': $printDefaults->printOrder = PageSetup::PAGEORDER_OVER_THEN_DOWN; break; case 'PaperSizeIndex': $printDefaults->paperSize = (int) $printValue ?: 9; break; case 'Scale': $printDefaults->scale = (int) $printValue ?: 100; break; } } } } return $printDefaults; } private function setLayout(stdClass $printDefaults, SimpleXMLElement $pageSetupAttributes): void { $printDefaults->orientation = (string) strtolower($pageSetupAttributes->Orientation ?? '') ?: PageSetup::ORIENTATION_PORTRAIT; $printDefaults->horizontalCentered = (bool) $pageSetupAttributes->CenterHorizontal ?: false; $printDefaults->verticalCentered = (bool) $pageSetupAttributes->CenterVertical ?: false; } private function setMargins(stdClass $printDefaults, SimpleXMLElement $pageSetupAttributes): void { $printDefaults->leftMargin = (float) $pageSetupAttributes->Left ?: 1.0; $printDefaults->rightMargin = (float) $pageSetupAttributes->Right ?: 1.0; $printDefaults->topMargin = (float) $pageSetupAttributes->Top ?: 1.0; $printDefaults->bottomMargin = (float) $pageSetupAttributes->Bottom ?: 1.0; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php000064400000017011152427537250021027 0ustar00 DataValidation::OPERATOR_BETWEEN, 'equal' => DataValidation::OPERATOR_EQUAL, 'greater' => DataValidation::OPERATOR_GREATERTHAN, 'greaterorequal' => DataValidation::OPERATOR_GREATERTHANOREQUAL, 'less' => DataValidation::OPERATOR_LESSTHAN, 'lessorequal' => DataValidation::OPERATOR_LESSTHANOREQUAL, 'notbetween' => DataValidation::OPERATOR_NOTBETWEEN, 'notequal' => DataValidation::OPERATOR_NOTEQUAL, ]; private const TYPE_MAPPINGS = [ 'textlength' => DataValidation::TYPE_TEXTLENGTH, ]; private int $thisRow = 0; private int $thisColumn = 0; private function replaceR1C1(array $matches): string { return AddressHelper::convertToA1($matches[0], $this->thisRow, $this->thisColumn, false); } public function loadDataValidations(SimpleXMLElement $worksheet, Spreadsheet $spreadsheet): void { $xmlX = $worksheet->children(Namespaces::URN_EXCEL); $sheet = $spreadsheet->getActiveSheet(); /** @var callable */ $pregCallback = [$this, 'replaceR1C1']; foreach ($xmlX->DataValidation as $dataValidation) { $cells = []; $validation = new DataValidation(); // set defaults $validation->setShowDropDown(true); $validation->setShowInputMessage(true); $validation->setShowErrorMessage(true); $validation->setShowDropDown(true); $this->thisRow = 1; $this->thisColumn = 1; foreach ($dataValidation as $tagName => $tagValue) { $tagValue = (string) $tagValue; $tagValueLower = strtolower($tagValue); switch ($tagName) { case 'Range': foreach (explode(',', $tagValue) as $range) { $cell = ''; if (preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) { // range $firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2]) . $selectionMatches[1]; $cell = $firstCell . ':' . Coordinate::stringFromColumnIndex((int) $selectionMatches[4]) . $selectionMatches[3]; $this->thisRow = (int) $selectionMatches[1]; $this->thisColumn = (int) $selectionMatches[2]; $sheet->getCell($firstCell); } elseif (preg_match('/^R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) { // cell $cell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2]) . $selectionMatches[1]; $sheet->getCell($cell); $this->thisRow = (int) $selectionMatches[1]; $this->thisColumn = (int) $selectionMatches[2]; } elseif (preg_match('/^C(\d+)$/', (string) $range, $selectionMatches) === 1) { // column $firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[1]) . '1'; $cell = $firstCell . ':' . Coordinate::stringFromColumnIndex((int) $selectionMatches[1]) . ((string) AddressRange::MAX_ROW); $this->thisColumn = (int) $selectionMatches[1]; $sheet->getCell($firstCell); } elseif (preg_match('/^R(\d+)$/', (string) $range, $selectionMatches)) { // row $firstCell = 'A' . $selectionMatches[1]; $cell = $firstCell . ':' . AddressRange::MAX_COLUMN . $selectionMatches[1]; $this->thisRow = (int) $selectionMatches[1]; $sheet->getCell($firstCell); } $validation->setSqref($cell); $stRange = $sheet->shrinkRangeToFit($cell); $cells = array_merge($cells, Coordinate::extractAllCellReferencesInRange($stRange)); } break; case 'Type': $validation->setType(self::TYPE_MAPPINGS[$tagValueLower] ?? $tagValueLower); break; case 'Qualifier': $validation->setOperator(self::OPERATOR_MAPPINGS[$tagValueLower] ?? $tagValueLower); break; case 'InputTitle': $validation->setPromptTitle($tagValue); break; case 'InputMessage': $validation->setPrompt($tagValue); break; case 'InputHide': $validation->setShowInputMessage(false); break; case 'ErrorStyle': $validation->setErrorStyle($tagValueLower); break; case 'ErrorTitle': $validation->setErrorTitle($tagValue); break; case 'ErrorMessage': $validation->setError($tagValue); break; case 'ErrorHide': $validation->setShowErrorMessage(false); break; case 'ComboHide': $validation->setShowDropDown(false); break; case 'UseBlank': $validation->setAllowBlank(true); break; case 'CellRangeList': // FIXME missing FIXME break; case 'Min': case 'Value': $tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue); $validation->setFormula1($tagValue); break; case 'Max': $tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue); $validation->setFormula2($tagValue); break; } } foreach ($cells as $cell) { $sheet->getCell($cell)->setDataValidation(clone $validation); } } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php000064400000010064152427537250017061 0ustar00Styles) || !is_iterable($xml->Styles[0])) { return []; } $alignmentStyleParser = new Style\Alignment(); $borderStyleParser = new Style\Border(); $fontStyleParser = new Style\Font(); $fillStyleParser = new Style\Fill(); $numberFormatStyleParser = new Style\NumberFormat(); foreach ($xml->Styles[0] as $style) { $style_ss = self::getAttributes($style, $namespaces['ss']); $styleID = (string) $style_ss['ID']; $this->styles[$styleID] = $this->styles['Default'] ?? []; $alignment = $border = $font = $fill = $numberFormat = $protection = []; foreach ($style as $styleType => $styleDatax) { $styleData = self::getSxml($styleDatax); $styleAttributes = $styleData->attributes($namespaces['ss']); switch ($styleType) { case 'Alignment': if ($styleAttributes) { $alignment = $alignmentStyleParser->parseStyle($styleAttributes); } break; case 'Borders': $border = $borderStyleParser->parseStyle($styleData, $namespaces); break; case 'Font': if ($styleAttributes) { $font = $fontStyleParser->parseStyle($styleAttributes); } break; case 'Interior': if ($styleAttributes) { $fill = $fillStyleParser->parseStyle($styleAttributes); } break; case 'NumberFormat': if ($styleAttributes) { $numberFormat = $numberFormatStyleParser->parseStyle($styleAttributes); } break; case 'Protection': $locked = $hidden = null; $styleAttributesP = $styleData->attributes($namespaces['x']); if (isset($styleAttributes['Protected'])) { $locked = ((bool) (string) $styleAttributes['Protected']) ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED; } if (isset($styleAttributesP['HideFormula'])) { $hidden = ((bool) (string) $styleAttributesP['HideFormula']) ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED; } if ($locked !== null || $hidden !== null) { $protection['protection'] = []; if ($locked !== null) { $protection['protection']['locked'] = $locked; } if ($hidden !== null) { $protection['protection']['hidden'] = $hidden; } } break; } } $this->styles[$styleID] = array_merge($alignment, $border, $font, $fill, $numberFormat, $protection); } return $this->styles; } private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement { return ($simple === null) ? new SimpleXMLElement('') : ($simple->attributes($node) ?? new SimpleXMLElement('')); } private static function getSxml(?SimpleXMLElement $simple): SimpleXMLElement { return ($simple !== null) ? $simple : new SimpleXMLElement(''); } } phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php000064400000012463152427537250021133 0ustar00spreadsheet = $spreadsheet; } private function docPropertiesOld(SimpleXMLElement $gnmXML): void { $docProps = $this->spreadsheet->getProperties(); foreach ($gnmXML->Summary->Item as $summaryItem) { $propertyName = $summaryItem->name; $propertyValue = $summaryItem->{'val-string'}; switch ($propertyName) { case 'title': $docProps->setTitle(trim($propertyValue)); break; case 'comments': $docProps->setDescription(trim($propertyValue)); break; case 'keywords': $docProps->setKeywords(trim($propertyValue)); break; case 'category': $docProps->setCategory(trim($propertyValue)); break; case 'manager': $docProps->setManager(trim($propertyValue)); break; case 'author': $docProps->setCreator(trim($propertyValue)); $docProps->setLastModifiedBy(trim($propertyValue)); break; case 'company': $docProps->setCompany(trim($propertyValue)); break; } } } private function docPropertiesDC(SimpleXMLElement $officePropertyDC): void { $docProps = $this->spreadsheet->getProperties(); foreach ($officePropertyDC as $propertyName => $propertyValue) { $propertyValue = trim((string) $propertyValue); switch ($propertyName) { case 'title': $docProps->setTitle($propertyValue); break; case 'subject': $docProps->setSubject($propertyValue); break; case 'creator': $docProps->setCreator($propertyValue); $docProps->setLastModifiedBy($propertyValue); break; case 'date': $creationDate = $propertyValue; $docProps->setModified($creationDate); break; case 'description': $docProps->setDescription($propertyValue); break; } } } private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta): void { $docProps = $this->spreadsheet->getProperties(); foreach ($officePropertyMeta as $propertyName => $propertyValue) { if ($propertyValue !== null) { $attributes = $propertyValue->attributes(Gnumeric::NAMESPACE_META); $propertyValue = trim((string) $propertyValue); switch ($propertyName) { case 'keyword': $docProps->setKeywords($propertyValue); break; case 'initial-creator': $docProps->setCreator($propertyValue); $docProps->setLastModifiedBy($propertyValue); break; case 'creation-date': $creationDate = $propertyValue; $docProps->setCreated($creationDate); break; case 'user-defined': if ($attributes) { [, $attrName] = explode(':', (string) $attributes['name']); $this->userDefinedProperties($attrName, $propertyValue); } break; } } } } private function userDefinedProperties(string $attrName, string $propertyValue): void { $docProps = $this->spreadsheet->getProperties(); switch ($attrName) { case 'publisher': $docProps->setCompany($propertyValue); break; case 'category': $docProps->setCategory($propertyValue); break; case 'manager': $docProps->setManager($propertyValue); break; } } public function readProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML): void { $officeXML = $xml->children(Gnumeric::NAMESPACE_OFFICE); if (!empty($officeXML)) { $officeDocXML = $officeXML->{'document-meta'}; $officeDocMetaXML = $officeDocXML->meta; foreach ($officeDocMetaXML as $officePropertyData) { $officePropertyDC = $officePropertyData->children(Gnumeric::NAMESPACE_DC); $this->docPropertiesDC($officePropertyDC); $officePropertyMeta = $officePropertyData->children(Gnumeric::NAMESPACE_META); $this->docPropertiesMeta($officePropertyMeta); } } elseif (isset($gnmXML->Summary)) { $this->docPropertiesOld($gnmXML); } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php000064400000012177152427537250020676 0ustar00spreadsheet = $spreadsheet; } public function printInformation(SimpleXMLElement $sheet): self { if (isset($sheet->PrintInformation, $sheet->PrintInformation[0])) { $printInformation = $sheet->PrintInformation[0]; $setup = $this->spreadsheet->getActiveSheet()->getPageSetup(); $attributes = $printInformation->Scale->attributes(); if (isset($attributes['percentage'])) { $setup->setScale((int) $attributes['percentage']); } $pageOrder = (string) $printInformation->order; if ($pageOrder === 'r_then_d') { $setup->setPageOrder(WorksheetPageSetup::PAGEORDER_OVER_THEN_DOWN); } elseif ($pageOrder === 'd_then_r') { $setup->setPageOrder(WorksheetPageSetup::PAGEORDER_DOWN_THEN_OVER); } $orientation = (string) $printInformation->orientation; if ($orientation !== '') { $setup->setOrientation($orientation); } $attributes = $printInformation->hcenter->attributes(); if (isset($attributes['value'])) { $setup->setHorizontalCentered((bool) (string) $attributes['value']); } $attributes = $printInformation->vcenter->attributes(); if (isset($attributes['value'])) { $setup->setVerticalCentered((bool) (string) $attributes['value']); } } return $this; } public function sheetMargins(SimpleXMLElement $sheet): self { if (isset($sheet->PrintInformation, $sheet->PrintInformation->Margins)) { $marginSet = [ // Default Settings 'top' => 0.75, 'header' => 0.3, 'left' => 0.7, 'right' => 0.7, 'bottom' => 0.75, 'footer' => 0.3, ]; $marginSet = $this->buildMarginSet($sheet, $marginSet); $this->adjustMargins($marginSet); } return $this; } private function buildMarginSet(SimpleXMLElement $sheet, array $marginSet): array { foreach ($sheet->PrintInformation->Margins->children(Gnumeric::NAMESPACE_GNM) as $key => $margin) { $marginAttributes = $margin->attributes(); $marginSize = ($marginAttributes['Points']) ?? 72; // Default is 72pt // Convert value in points to inches $marginSize = PageMargins::fromPoints((float) $marginSize); $marginSet[$key] = $marginSize; } return $marginSet; } private function adjustMargins(array $marginSet): void { foreach ($marginSet as $key => $marginSize) { // Gnumeric is quirky in the way it displays the header/footer values: // header is actually the sum of top and header; footer is actually the sum of bottom and footer // then top is actually the header value, and bottom is actually the footer value switch ($key) { case 'left': case 'right': $this->sheetMargin($key, $marginSize); break; case 'top': $this->sheetMargin($key, $marginSet['header'] ?? 0); break; case 'bottom': $this->sheetMargin($key, $marginSet['footer'] ?? 0); break; case 'header': $this->sheetMargin($key, ($marginSet['top'] ?? 0) - $marginSize); break; case 'footer': $this->sheetMargin($key, ($marginSet['bottom'] ?? 0) - $marginSize); break; } } } private function sheetMargin(string $key, float $marginSize): void { switch ($key) { case 'top': $this->spreadsheet->getActiveSheet()->getPageMargins()->setTop($marginSize); break; case 'bottom': $this->spreadsheet->getActiveSheet()->getPageMargins()->setBottom($marginSize); break; case 'left': $this->spreadsheet->getActiveSheet()->getPageMargins()->setLeft($marginSize); break; case 'right': $this->spreadsheet->getActiveSheet()->getPageMargins()->setRight($marginSize); break; case 'header': $this->spreadsheet->getActiveSheet()->getPageMargins()->setHeader($marginSize); break; case 'footer': $this->spreadsheet->getActiveSheet()->getPageMargins()->setFooter($marginSize); break; } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php000064400000027341152427537250020263 0ustar00 [ '0' => Border::BORDER_NONE, '1' => Border::BORDER_THIN, '2' => Border::BORDER_MEDIUM, '3' => Border::BORDER_SLANTDASHDOT, '4' => Border::BORDER_DASHED, '5' => Border::BORDER_THICK, '6' => Border::BORDER_DOUBLE, '7' => Border::BORDER_DOTTED, '8' => Border::BORDER_MEDIUMDASHED, '9' => Border::BORDER_DASHDOT, '10' => Border::BORDER_MEDIUMDASHDOT, '11' => Border::BORDER_DASHDOTDOT, '12' => Border::BORDER_MEDIUMDASHDOTDOT, '13' => Border::BORDER_MEDIUMDASHDOTDOT, ], 'fillType' => [ '1' => Fill::FILL_SOLID, '2' => Fill::FILL_PATTERN_DARKGRAY, '3' => Fill::FILL_PATTERN_MEDIUMGRAY, '4' => Fill::FILL_PATTERN_LIGHTGRAY, '5' => Fill::FILL_PATTERN_GRAY125, '6' => Fill::FILL_PATTERN_GRAY0625, '7' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe '8' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe '9' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe '10' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe '11' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch '12' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch '13' => Fill::FILL_PATTERN_LIGHTHORIZONTAL, '14' => Fill::FILL_PATTERN_LIGHTVERTICAL, '15' => Fill::FILL_PATTERN_LIGHTUP, '16' => Fill::FILL_PATTERN_LIGHTDOWN, '17' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch '18' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch ], 'horizontal' => [ '1' => Alignment::HORIZONTAL_GENERAL, '2' => Alignment::HORIZONTAL_LEFT, '4' => Alignment::HORIZONTAL_RIGHT, '8' => Alignment::HORIZONTAL_CENTER, '16' => Alignment::HORIZONTAL_CENTER_CONTINUOUS, '32' => Alignment::HORIZONTAL_JUSTIFY, '64' => Alignment::HORIZONTAL_CENTER_CONTINUOUS, ], 'underline' => [ '1' => Font::UNDERLINE_SINGLE, '2' => Font::UNDERLINE_DOUBLE, '3' => Font::UNDERLINE_SINGLEACCOUNTING, '4' => Font::UNDERLINE_DOUBLEACCOUNTING, ], 'vertical' => [ '1' => Alignment::VERTICAL_TOP, '2' => Alignment::VERTICAL_BOTTOM, '4' => Alignment::VERTICAL_CENTER, '8' => Alignment::VERTICAL_JUSTIFY, ], ]; public function __construct(Spreadsheet $spreadsheet, bool $readDataOnly) { $this->spreadsheet = $spreadsheet; $this->readDataOnly = $readDataOnly; } public function read(SimpleXMLElement $sheet, int $maxRow, int $maxCol): void { if ($sheet->Styles->StyleRegion !== null) { $this->readStyles($sheet->Styles->StyleRegion, $maxRow, $maxCol); } } private function readStyles(SimpleXMLElement $styleRegion, int $maxRow, int $maxCol): void { foreach ($styleRegion as $style) { /** @scrutinizer ignore-call */ $styleAttributes = $style->attributes(); if ($styleAttributes !== null && ($styleAttributes['startRow'] <= $maxRow) && ($styleAttributes['startCol'] <= $maxCol)) { $cellRange = $this->readStyleRange($styleAttributes, $maxCol, $maxRow); $styleAttributes = $style->Style->attributes(); $styleArray = []; // We still set the number format mask for date/time values, even if readDataOnly is true // so that we can identify whether a float is a float or a date value $formatCode = $styleAttributes ? (string) $styleAttributes['Format'] : null; if ($formatCode && Date::isDateTimeFormatCode($formatCode)) { $styleArray['numberFormat']['formatCode'] = $formatCode; } if ($this->readDataOnly === false && $styleAttributes !== null) { // If readDataOnly is false, we set all formatting information $styleArray['numberFormat']['formatCode'] = $formatCode; $styleArray = $this->readStyle($styleArray, $styleAttributes, /** @scrutinizer ignore-type */ $style); } $this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray); } } } private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void { if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) { $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes()); $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH; } elseif (isset($srssb->Diagonal)) { $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes()); $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP; } elseif (isset($srssb->{'Rev-Diagonal'})) { $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes()); $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN; } } private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void { $ucDirection = ucfirst($direction); if (isset($srssb->$ucDirection)) { $styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes()); } } private function calcRotation(SimpleXMLElement $styleAttributes): int { $rotation = (int) $styleAttributes->Rotation; if ($rotation >= 270 && $rotation <= 360) { $rotation -= 360; } $rotation = (abs($rotation) > 90) ? 0 : $rotation; return $rotation; } private static function addStyle(array &$styleArray, string $key, string $value): void { if (array_key_exists($value, self::$mappings[$key])) { $styleArray[$key] = self::$mappings[$key][$value]; } } private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void { if (array_key_exists($value, self::$mappings[$key])) { $styleArray[$key1][$key] = self::$mappings[$key][$value]; } } private static function parseBorderAttributes(?SimpleXMLElement $borderAttributes): array { $styleArray = []; if ($borderAttributes !== null) { if (isset($borderAttributes['Color'])) { $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']); } self::addStyle($styleArray, 'borderStyle', (string) $borderAttributes['Style']); } return $styleArray; } private static function parseGnumericColour(string $gnmColour): string { [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour); $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2); $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2); $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2); return $gnmR . $gnmG . $gnmB; } private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void { $RGB = self::parseGnumericColour((string) $styleAttributes['Fore']); $styleArray['font']['color']['rgb'] = $RGB; $RGB = self::parseGnumericColour((string) $styleAttributes['Back']); $shade = (string) $styleAttributes['Shade']; if (($RGB !== '000000') || ($shade !== '0')) { $RGB2 = self::parseGnumericColour((string) $styleAttributes['PatternColor']); if ($shade === '1') { $styleArray['fill']['startColor']['rgb'] = $RGB; $styleArray['fill']['endColor']['rgb'] = $RGB2; } else { $styleArray['fill']['endColor']['rgb'] = $RGB; $styleArray['fill']['startColor']['rgb'] = $RGB2; } self::addStyle2($styleArray, 'fill', 'fillType', $shade); } } private function readStyleRange(SimpleXMLElement $styleAttributes, int $maxCol, int $maxRow): string { $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1); $startRow = $styleAttributes['startRow'] + 1; $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol']; $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1); $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']); $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow; return $cellRange; } private function readStyle(array $styleArray, SimpleXMLElement $styleAttributes, SimpleXMLElement $style): array { self::addStyle2($styleArray, 'alignment', 'horizontal', (string) $styleAttributes['HAlign']); self::addStyle2($styleArray, 'alignment', 'vertical', (string) $styleAttributes['VAlign']); $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1'; $styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes); $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1'; $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0; $this->addColors($styleArray, $styleAttributes); $fontAttributes = $style->Style->Font->attributes(); if ($fontAttributes !== null) { $styleArray['font']['name'] = (string) $style->Style->Font; $styleArray['font']['size'] = (int) ($fontAttributes['Unit']); $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1'; $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1'; $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1'; self::addStyle2($styleArray, 'font', 'underline', (string) $fontAttributes['Underline']); switch ($fontAttributes['Script']) { case '1': $styleArray['font']['superscript'] = true; break; case '-1': $styleArray['font']['subscript'] = true; break; } } if (isset($style->Style->StyleBorder)) { $srssb = $style->Style->StyleBorder; $this->addBorderStyle($srssb, $styleArray, 'top'); $this->addBorderStyle($srssb, $styleArray, 'bottom'); $this->addBorderStyle($srssb, $styleArray, 'left'); $this->addBorderStyle($srssb, $styleArray, 'right'); $this->addBorderDiagonal($srssb, $styleArray); } // TO DO /* if (isset($style->Style->HyperLink)) { $hyperlink = $style->Style->HyperLink->attributes(); } */ return $styleArray; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php000064400000010775152427537250017703 0ustar00fileHandle = $fileHandle; $this->escapeCharacter = $escapeCharacter; $this->enclosure = $enclosure; $this->countPotentialDelimiters(); } public function getDefaultDelimiter(): string { return self::POTENTIAL_DELIMETERS[0]; } public function linesCounted(): int { return $this->numberLines; } protected function countPotentialDelimiters(): void { $this->counts = array_fill_keys(self::POTENTIAL_DELIMETERS, []); $delimiterKeys = array_flip(self::POTENTIAL_DELIMETERS); // Count how many times each of the potential delimiters appears in each line $this->numberLines = 0; while (($line = $this->getNextLine()) !== false && (++$this->numberLines < 1000)) { $this->countDelimiterValues($line, $delimiterKeys); } } protected function countDelimiterValues(string $line, array $delimiterKeys): void { $splitString = str_split($line, 1); if (is_array($splitString)) { $distribution = array_count_values($splitString); $countLine = array_intersect_key($distribution, $delimiterKeys); foreach (self::POTENTIAL_DELIMETERS as $delimiter) { $this->counts[$delimiter][] = $countLine[$delimiter] ?? 0; } } } public function infer(): ?string { // Calculate the mean square deviations for each delimiter // (ignoring delimiters that haven't been found consistently) $meanSquareDeviations = []; $middleIdx = floor(($this->numberLines - 1) / 2); foreach (self::POTENTIAL_DELIMETERS as $delimiter) { $series = $this->counts[$delimiter]; sort($series); $median = ($this->numberLines % 2) ? $series[$middleIdx] : ($series[$middleIdx] + $series[$middleIdx + 1]) / 2; if ($median === 0) { continue; } $meanSquareDeviations[$delimiter] = array_reduce( $series, function ($sum, $value) use ($median) { return $sum + ($value - $median) ** 2; } ) / count($series); } // ... and pick the delimiter with the smallest mean square deviation // (in case of ties, the order in potentialDelimiters is respected) $min = INF; foreach (self::POTENTIAL_DELIMETERS as $delimiter) { if (!isset($meanSquareDeviations[$delimiter])) { continue; } if ($meanSquareDeviations[$delimiter] < $min) { $min = $meanSquareDeviations[$delimiter]; $this->delimiter = $delimiter; } } return $this->delimiter; } /** * Get the next full line from the file. * * @return false|string */ public function getNextLine() { $line = ''; $enclosure = ($this->escapeCharacter === '' ? '' : ('(?escapeCharacter, '/') . ')')) . preg_quote($this->enclosure, '/'); do { // Get the next line in the file $newLine = fgets($this->fileHandle); // Return false if there is no next line if ($newLine === false) { return false; } // Add the new line to the line passed in $line = $line . $newLine; // Drop everything that is enclosed to avoid counting false positives in enclosures $line = (string) preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line); // See if we have any enclosures left in the line // if we still have an enclosure then we need to read the next line as well } while (preg_match('/(' . $enclosure . ')/', $line) > 0); return ($line !== '') ? $line : false; } } phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php000064400000011132152427537250016531 0ustar00data. * * @var int */ private $dataSize; /** * Current position in stream. * * @var int */ private $pos; /** * Workbook to be returned by the reader. * * @var Spreadsheet */ private $spreadsheet; /** * Worksheet that is currently being built by the reader. * * @var Worksheet */ private $phpSheet; /** * BIFF version. * * @var int */ private $version; /** * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95) * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'. * * @var string */ private $codepage; /** * Shared formats. * * @var array */ private $formats; /** * Shared fonts. * * @var Font[] */ private $objFonts; /** * Color palette. * * @var array */ private $palette; /** * Worksheets. * * @var array */ private $sheets; /** * External books. * * @var array */ private $externalBooks; /** * REF structures. Only applies to BIFF8. * * @var array */ private $ref; /** * External names. * * @var array */ private $externalNames; /** * Defined names. * * @var array */ private $definedname; /** * Shared strings. Only applies to BIFF8. * * @var array */ private $sst; /** * Panes are frozen? (in sheet currently being read). See WINDOW2 record. * * @var bool */ private $frozen; /** * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record. * * @var bool */ private $isFitToPages; /** * Objects. One OBJ record contributes with one entry. * * @var array */ private $objs; /** * Text Objects. One TXO record corresponds with one entry. * * @var array */ private $textObjects; /** * Cell Annotations (BIFF8). * * @var array */ private $cellNotes; /** * The combined MSODRAWINGGROUP data. * * @var string */ private $drawingGroupData; /** * The combined MSODRAWING data (per sheet). * * @var string */ private $drawingData; /** * Keep track of XF index. * * @var int */ private $xfIndex; /** * Mapping of XF index (that is a cell XF) to final index in cellXf collection. * * @var array */ private $mapCellXfIndex; /** * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection. * * @var array */ private $mapCellStyleXfIndex; /** * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value. * * @var array */ private $sharedFormulas; /** * The shared formula parts in a sheet. One FORMULA record contributes with one value if it * refers to a shared formula. * * @var array */ private $sharedFormulaParts; /** * The type of encryption in use. * * @var int */ private $encryption = 0; /** * The position in the stream after which contents are encrypted. * * @var int */ private $encryptionStartPos = 0; /** * The current RC4 decryption object. * * @var ?Xls\RC4 */ private $rc4Key; /** * The position in the stream that the RC4 decryption object was left at. * * @var int */ private $rc4Pos = 0; /** * The current MD5 context state. * It is never set in the program, so code which uses it is suspect. * * @var string */ private $md5Ctxt; // @phpstan-ignore-line /** * @var int */ private $textObjRef; /** * @var string */ private $baseCell; /** * Create a new Xls Reader instance. */ public function __construct() { parent::__construct(); } /** * Can the current IReader read the file? */ public function canRead(string $filename): bool { if (File::testFileNoThrow($filename) === false) { return false; } try { // Use ParseXL for the hard work. $ole = new OLERead(); // get excel data $ole->read($filename); if ($ole->wrkbook === null) { throw new Exception('The filename ' . $filename . ' is not recognised as a Spreadsheet file'); } return true; } catch (PhpSpreadsheetException $e) { return false; } } public function setCodepage(string $codepage): void { if (CodePage::validate($codepage) === false) { throw new PhpSpreadsheetException('Unknown codepage: ' . $codepage); } $this->codepage = $codepage; } /** * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object. * * @param string $filename * * @return array */ public function listWorksheetNames($filename) { File::assertFile($filename); $worksheetNames = []; // Read the OLE file $this->loadOLE($filename); // total byte size of Excel data (workbook global substream + sheet substreams) $this->dataSize = strlen($this->data); $this->pos = 0; $this->sheets = []; // Parse Workbook Global Substream while ($this->pos < $this->dataSize) { $code = self::getUInt2d($this->data, $this->pos); switch ($code) { case self::XLS_TYPE_BOF: $this->readBof(); break; case self::XLS_TYPE_SHEET: $this->readSheet(); break; case self::XLS_TYPE_EOF: $this->readDefault(); break 2; default: $this->readDefault(); break; } } foreach ($this->sheets as $sheet) { if ($sheet['sheetType'] != 0x00) { // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module continue; } $worksheetNames[] = $sheet['name']; } return $worksheetNames; } /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * * @param string $filename * * @return array */ public function listWorksheetInfo($filename) { File::assertFile($filename); $worksheetInfo = []; // Read the OLE file $this->loadOLE($filename); // total byte size of Excel data (workbook global substream + sheet substreams) $this->dataSize = strlen($this->data); // initialize $this->pos = 0; $this->sheets = []; // Parse Workbook Global Substream while ($this->pos < $this->dataSize) { $code = self::getUInt2d($this->data, $this->pos); switch ($code) { case self::XLS_TYPE_BOF: $this->readBof(); break; case self::XLS_TYPE_SHEET: $this->readSheet(); break; case self::XLS_TYPE_EOF: $this->readDefault(); break 2; default: $this->readDefault(); break; } } // Parse the individual sheets foreach ($this->sheets as $sheet) { if ($sheet['sheetType'] != 0x00) { // 0x00: Worksheet // 0x02: Chart // 0x06: Visual Basic module continue; } $tmpInfo = []; $tmpInfo['worksheetName'] = $sheet['name']; $tmpInfo['lastColumnLetter'] = 'A'; $tmpInfo['lastColumnIndex'] = 0; $tmpInfo['totalRows'] = 0; $tmpInfo['totalColumns'] = 0; $this->pos = $sheet['offset']; while ($this->pos <= $this->dataSize - 4) { $code = self::getUInt2d($this->data, $this->pos); switch ($code) { case self::XLS_TYPE_RK: case self::XLS_TYPE_LABELSST: case self::XLS_TYPE_NUMBER: case self::XLS_TYPE_FORMULA: case self::XLS_TYPE_BOOLERR: case self::XLS_TYPE_LABEL: $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; $rowIndex = self::getUInt2d($recordData, 0) + 1; $columnIndex = self::getUInt2d($recordData, 2); $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex); $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex); break; case self::XLS_TYPE_BOF: $this->readBof(); break; case self::XLS_TYPE_EOF: $this->readDefault(); break 2; default: $this->readDefault(); break; } } $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1; $worksheetInfo[] = $tmpInfo; } return $worksheetInfo; } /** * Loads PhpSpreadsheet from file. */ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Read the OLE file $this->loadOLE($filename); // Initialisations $this->spreadsheet = new Spreadsheet(); $this->spreadsheet->removeSheetByIndex(0); // remove 1st sheet if (!$this->readDataOnly) { $this->spreadsheet->removeCellStyleXfByIndex(0); // remove the default style $this->spreadsheet->removeCellXfByIndex(0); // remove the default style } // Read the summary information stream (containing meta data) $this->readSummaryInformation(); // Read the Additional document summary information stream (containing application-specific meta data) $this->readDocumentSummaryInformation(); // total byte size of Excel data (workbook global substream + sheet substreams) $this->dataSize = strlen($this->data); // initialize $this->pos = 0; $this->codepage = $this->codepage ?: CodePage::DEFAULT_CODE_PAGE; $this->formats = []; $this->objFonts = []; $this->palette = []; $this->sheets = []; $this->externalBooks = []; $this->ref = []; $this->definedname = []; $this->sst = []; $this->drawingGroupData = ''; $this->xfIndex = 0; $this->mapCellXfIndex = []; $this->mapCellStyleXfIndex = []; // Parse Workbook Global Substream while ($this->pos < $this->dataSize) { $code = self::getUInt2d($this->data, $this->pos); switch ($code) { case self::XLS_TYPE_BOF: $this->readBof(); break; case self::XLS_TYPE_FILEPASS: $this->readFilepass(); break; case self::XLS_TYPE_CODEPAGE: $this->readCodepage(); break; case self::XLS_TYPE_DATEMODE: $this->readDateMode(); break; case self::XLS_TYPE_FONT: $this->readFont(); break; case self::XLS_TYPE_FORMAT: $this->readFormat(); break; case self::XLS_TYPE_XF: $this->readXf(); break; case self::XLS_TYPE_XFEXT: $this->readXfExt(); break; case self::XLS_TYPE_STYLE: $this->readStyle(); break; case self::XLS_TYPE_PALETTE: $this->readPalette(); break; case self::XLS_TYPE_SHEET: $this->readSheet(); break; case self::XLS_TYPE_EXTERNALBOOK: $this->readExternalBook(); break; case self::XLS_TYPE_EXTERNNAME: $this->readExternName(); break; case self::XLS_TYPE_EXTERNSHEET: $this->readExternSheet(); break; case self::XLS_TYPE_DEFINEDNAME: $this->readDefinedName(); break; case self::XLS_TYPE_MSODRAWINGGROUP: $this->readMsoDrawingGroup(); break; case self::XLS_TYPE_SST: $this->readSst(); break; case self::XLS_TYPE_EOF: $this->readDefault(); break 2; default: $this->readDefault(); break; } } // Resolve indexed colors for font, fill, and border colors // Cannot be resolved already in XF record, because PALETTE record comes afterwards if (!$this->readDataOnly) { foreach ($this->objFonts as $objFont) { if (isset($objFont->colorIndex)) { $color = Xls\Color::map($objFont->colorIndex, $this->palette, $this->version); $objFont->getColor()->setRGB($color['rgb']); } } foreach ($this->spreadsheet->getCellXfCollection() as $objStyle) { // fill start and end color $fill = $objStyle->getFill(); if (isset($fill->startcolorIndex)) { $startColor = Xls\Color::map($fill->startcolorIndex, $this->palette, $this->version); $fill->getStartColor()->setRGB($startColor['rgb']); } if (isset($fill->endcolorIndex)) { $endColor = Xls\Color::map($fill->endcolorIndex, $this->palette, $this->version); $fill->getEndColor()->setRGB($endColor['rgb']); } // border colors $top = $objStyle->getBorders()->getTop(); $right = $objStyle->getBorders()->getRight(); $bottom = $objStyle->getBorders()->getBottom(); $left = $objStyle->getBorders()->getLeft(); $diagonal = $objStyle->getBorders()->getDiagonal(); if (isset($top->colorIndex)) { $borderTopColor = Xls\Color::map($top->colorIndex, $this->palette, $this->version); $top->getColor()->setRGB($borderTopColor['rgb']); } if (isset($right->colorIndex)) { $borderRightColor = Xls\Color::map($right->colorIndex, $this->palette, $this->version); $right->getColor()->setRGB($borderRightColor['rgb']); } if (isset($bottom->colorIndex)) { $borderBottomColor = Xls\Color::map($bottom->colorIndex, $this->palette, $this->version); $bottom->getColor()->setRGB($borderBottomColor['rgb']); } if (isset($left->colorIndex)) { $borderLeftColor = Xls\Color::map($left->colorIndex, $this->palette, $this->version); $left->getColor()->setRGB($borderLeftColor['rgb']); } if (isset($diagonal->colorIndex)) { $borderDiagonalColor = Xls\Color::map($diagonal->colorIndex, $this->palette, $this->version); $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']); } } } // treat MSODRAWINGGROUP records, workbook-level Escher $escherWorkbook = null; if (!$this->readDataOnly && $this->drawingGroupData) { $escher = new Escher(); $reader = new Xls\Escher($escher); $escherWorkbook = $reader->load($this->drawingGroupData); } // Parse the individual sheets foreach ($this->sheets as $sheet) { if ($sheet['sheetType'] != 0x00) { // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module continue; } // check if sheet should be skipped if (isset($this->loadSheetsOnly) && !in_array($sheet['name'], $this->loadSheetsOnly)) { continue; } // add sheet to PhpSpreadsheet object $this->phpSheet = $this->spreadsheet->createSheet(); // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet // name in line with the formula, not the reverse $this->phpSheet->setTitle($sheet['name'], false, false); $this->phpSheet->setSheetState($sheet['sheetState']); $this->pos = $sheet['offset']; // Initialize isFitToPages. May change after reading SHEETPR record. $this->isFitToPages = false; // Initialize drawingData $this->drawingData = ''; // Initialize objs $this->objs = []; // Initialize shared formula parts $this->sharedFormulaParts = []; // Initialize shared formulas $this->sharedFormulas = []; // Initialize text objs $this->textObjects = []; // Initialize cell annotations $this->cellNotes = []; $this->textObjRef = -1; while ($this->pos <= $this->dataSize - 4) { $code = self::getUInt2d($this->data, $this->pos); switch ($code) { case self::XLS_TYPE_BOF: $this->readBof(); break; case self::XLS_TYPE_PRINTGRIDLINES: $this->readPrintGridlines(); break; case self::XLS_TYPE_DEFAULTROWHEIGHT: $this->readDefaultRowHeight(); break; case self::XLS_TYPE_SHEETPR: $this->readSheetPr(); break; case self::XLS_TYPE_HORIZONTALPAGEBREAKS: $this->readHorizontalPageBreaks(); break; case self::XLS_TYPE_VERTICALPAGEBREAKS: $this->readVerticalPageBreaks(); break; case self::XLS_TYPE_HEADER: $this->readHeader(); break; case self::XLS_TYPE_FOOTER: $this->readFooter(); break; case self::XLS_TYPE_HCENTER: $this->readHcenter(); break; case self::XLS_TYPE_VCENTER: $this->readVcenter(); break; case self::XLS_TYPE_LEFTMARGIN: $this->readLeftMargin(); break; case self::XLS_TYPE_RIGHTMARGIN: $this->readRightMargin(); break; case self::XLS_TYPE_TOPMARGIN: $this->readTopMargin(); break; case self::XLS_TYPE_BOTTOMMARGIN: $this->readBottomMargin(); break; case self::XLS_TYPE_PAGESETUP: $this->readPageSetup(); break; case self::XLS_TYPE_PROTECT: $this->readProtect(); break; case self::XLS_TYPE_SCENPROTECT: $this->readScenProtect(); break; case self::XLS_TYPE_OBJECTPROTECT: $this->readObjectProtect(); break; case self::XLS_TYPE_PASSWORD: $this->readPassword(); break; case self::XLS_TYPE_DEFCOLWIDTH: $this->readDefColWidth(); break; case self::XLS_TYPE_COLINFO: $this->readColInfo(); break; case self::XLS_TYPE_DIMENSION: $this->readDefault(); break; case self::XLS_TYPE_ROW: $this->readRow(); break; case self::XLS_TYPE_DBCELL: $this->readDefault(); break; case self::XLS_TYPE_RK: $this->readRk(); break; case self::XLS_TYPE_LABELSST: $this->readLabelSst(); break; case self::XLS_TYPE_MULRK: $this->readMulRk(); break; case self::XLS_TYPE_NUMBER: $this->readNumber(); break; case self::XLS_TYPE_FORMULA: $this->readFormula(); break; case self::XLS_TYPE_SHAREDFMLA: $this->readSharedFmla(); break; case self::XLS_TYPE_BOOLERR: $this->readBoolErr(); break; case self::XLS_TYPE_MULBLANK: $this->readMulBlank(); break; case self::XLS_TYPE_LABEL: $this->readLabel(); break; case self::XLS_TYPE_BLANK: $this->readBlank(); break; case self::XLS_TYPE_MSODRAWING: $this->readMsoDrawing(); break; case self::XLS_TYPE_OBJ: $this->readObj(); break; case self::XLS_TYPE_WINDOW2: $this->readWindow2(); break; case self::XLS_TYPE_PAGELAYOUTVIEW: $this->readPageLayoutView(); break; case self::XLS_TYPE_SCL: $this->readScl(); break; case self::XLS_TYPE_PANE: $this->readPane(); break; case self::XLS_TYPE_SELECTION: $this->readSelection(); break; case self::XLS_TYPE_MERGEDCELLS: $this->readMergedCells(); break; case self::XLS_TYPE_HYPERLINK: $this->readHyperLink(); break; case self::XLS_TYPE_DATAVALIDATIONS: $this->readDataValidations(); break; case self::XLS_TYPE_DATAVALIDATION: $this->readDataValidation(); break; case self::XLS_TYPE_CFHEADER: $cellRangeAddresses = $this->readCFHeader(); break; case self::XLS_TYPE_CFRULE: $this->readCFRule($cellRangeAddresses ?? []); break; case self::XLS_TYPE_SHEETLAYOUT: $this->readSheetLayout(); break; case self::XLS_TYPE_SHEETPROTECTION: $this->readSheetProtection(); break; case self::XLS_TYPE_RANGEPROTECTION: $this->readRangeProtection(); break; case self::XLS_TYPE_NOTE: $this->readNote(); break; case self::XLS_TYPE_TXO: $this->readTextObject(); break; case self::XLS_TYPE_CONTINUE: $this->readContinue(); break; case self::XLS_TYPE_EOF: $this->readDefault(); break 2; default: $this->readDefault(); break; } } // treat MSODRAWING records, sheet-level Escher if (!$this->readDataOnly && $this->drawingData) { $escherWorksheet = new Escher(); $reader = new Xls\Escher($escherWorksheet); $escherWorksheet = $reader->load($this->drawingData); // get all spContainers in one long array, so they can be mapped to OBJ records /** @var SpContainer[] */ $allSpContainers = method_exists($escherWorksheet, 'getDgContainer') ? $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers() : []; } // treat OBJ records foreach ($this->objs as $n => $obj) { // the first shape container never has a corresponding OBJ record, hence $n + 1 if (isset($allSpContainers[$n + 1])) { $spContainer = $allSpContainers[$n + 1]; // we skip all spContainers that are a part of a group shape since we cannot yet handle those if ($spContainer->getNestingLevel() > 1) { continue; } // calculate the width and height of the shape /** @var int $startRow */ [$startColumn, $startRow] = Coordinate::coordinateFromString($spContainer->getStartCoordinates()); /** @var int $endRow */ [$endColumn, $endRow] = Coordinate::coordinateFromString($spContainer->getEndCoordinates()); $startOffsetX = $spContainer->getStartOffsetX(); $startOffsetY = $spContainer->getStartOffsetY(); $endOffsetX = $spContainer->getEndOffsetX(); $endOffsetY = $spContainer->getEndOffsetY(); $width = SharedXls::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX); $height = SharedXls::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY); // calculate offsetX and offsetY of the shape $offsetX = (int) ($startOffsetX * SharedXls::sizeCol($this->phpSheet, $startColumn) / 1024); $offsetY = (int) ($startOffsetY * SharedXls::sizeRow($this->phpSheet, $startRow) / 256); switch ($obj['otObjType']) { case 0x19: // Note if (isset($this->cellNotes[$obj['idObjID']])) { $cellNote = $this->cellNotes[$obj['idObjID']]; if (isset($this->textObjects[$obj['idObjID']])) { $textObject = $this->textObjects[$obj['idObjID']]; $this->cellNotes[$obj['idObjID']]['objTextData'] = $textObject; } } break; case 0x08: // picture // get index to BSE entry (1-based) $BSEindex = $spContainer->getOPT(0x0104); // If there is no BSE Index, we will fail here and other fields are not read. // Fix by checking here. // TODO: Why is there no BSE Index? Is this a new Office Version? Password protected field? // More likely : a uncompatible picture if (!$BSEindex) { continue 2; } if ($escherWorkbook) { $BSECollection = method_exists($escherWorkbook, 'getDggContainer') ? $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection() : []; $BSE = $BSECollection[$BSEindex - 1]; $blipType = $BSE->getBlipType(); // need check because some blip types are not supported by Escher reader such as EMF if ($blip = $BSE->getBlip()) { $ih = imagecreatefromstring($blip->getData()); if ($ih !== false) { $drawing = new MemoryDrawing(); $drawing->setImageResource($ih); // width, height, offsetX, offsetY $drawing->setResizeProportional(false); $drawing->setWidth($width); $drawing->setHeight($height); $drawing->setOffsetX($offsetX); $drawing->setOffsetY($offsetY); switch ($blipType) { case BSE::BLIPTYPE_JPEG: $drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG); $drawing->setMimeType(MemoryDrawing::MIMETYPE_JPEG); break; case BSE::BLIPTYPE_PNG: imagealphablending($ih, false); imagesavealpha($ih, true); $drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG); $drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG); break; } $drawing->setWorksheet($this->phpSheet); $drawing->setCoordinates($spContainer->getStartCoordinates()); } } } break; default: // other object type break; } } } // treat SHAREDFMLA records if ($this->version == self::XLS_BIFF8) { foreach ($this->sharedFormulaParts as $cell => $baseCell) { /** @var int $row */ [$column, $row] = Coordinate::coordinateFromString($cell); if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) { $formula = $this->getFormulaFromStructure($this->sharedFormulas[$baseCell], $cell); $this->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA); } } } if (!empty($this->cellNotes)) { foreach ($this->cellNotes as $note => $noteDetails) { if (!isset($noteDetails['objTextData'])) { if (isset($this->textObjects[$note])) { $textObject = $this->textObjects[$note]; $noteDetails['objTextData'] = $textObject; } else { $noteDetails['objTextData']['text'] = ''; } } $cellAddress = str_replace('$', '', $noteDetails['cellRef']); $this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text'])); } } } // add the named ranges (defined names) foreach ($this->definedname as $definedName) { if ($definedName['isBuiltInName']) { switch ($definedName['name']) { case pack('C', 0x06): // print area // in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2 $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma? $extractedRanges = []; foreach ($ranges as $range) { // $range should look like one of these // Foo!$C$7:$J$66 // Bar!$A$1:$IV$2 $explodes = Worksheet::extractSheetTitle($range, true); $sheetName = trim($explodes[0], "'"); if (count($explodes) == 2) { if (strpos($explodes[1], ':') === false) { $explodes[1] = $explodes[1] . ':' . $explodes[1]; } $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66 } } if ($docSheet = $this->spreadsheet->getSheetByName($sheetName)) { $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2 } break; case pack('C', 0x07): // print titles (repeating rows) // Assuming BIFF8, there are 3 cases // 1. repeating rows // formula looks like this: Sheet!$A$1:$IV$2 // rows 1-2 repeat // 2. repeating columns // formula looks like this: Sheet!$A$1:$B$65536 // columns A-B repeat // 3. both repeating rows and repeating columns // formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2 $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma? foreach ($ranges as $range) { // $range should look like this one of these // Sheet!$A$1:$B$65536 // Sheet!$A$1:$IV$2 if (strpos($range, '!') !== false) { $explodes = Worksheet::extractSheetTitle($range, true); if ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) { $extractedRange = $explodes[1]; $extractedRange = str_replace('$', '', $extractedRange); $coordinateStrings = explode(':', $extractedRange); if (count($coordinateStrings) == 2) { [$firstColumn, $firstRow] = Coordinate::coordinateFromString($coordinateStrings[0]); [$lastColumn, $lastRow] = Coordinate::coordinateFromString($coordinateStrings[1]); if ($firstColumn == 'A' && $lastColumn == 'IV') { // then we have repeating rows $docSheet->getPageSetup()->setRowsToRepeatAtTop([$firstRow, $lastRow]); } elseif ($firstRow == 1 && $lastRow == 65536) { // then we have repeating columns $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$firstColumn, $lastColumn]); } } } } } break; } } else { // Extract range if (strpos($definedName['formula'], '!') !== false) { $explodes = Worksheet::extractSheetTitle($definedName['formula'], true); if ( ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) || ($docSheet = $this->spreadsheet->getSheetByName(trim($explodes[0], "'"))) ) { $extractedRange = $explodes[1]; $localOnly = ($definedName['scope'] === 0) ? false : true; $scope = ($definedName['scope'] === 0) ? null : $this->spreadsheet->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']); $this->spreadsheet->addNamedRange(new NamedRange((string) $definedName['name'], $docSheet, $extractedRange, $localOnly, $scope)); } } // Named Value // TODO Provide support for named values } } $this->data = ''; return $this->spreadsheet; } /** * Read record data from stream, decrypting as required. * * @param string $data Data stream to read from * @param int $pos Position to start reading from * @param int $len Record data length * * @return string Record data */ private function readRecordData($data, $pos, $len) { $data = substr($data, $pos, $len); // File not encrypted, or record before encryption start point if ($this->encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->encryptionStartPos) { return $data; } $recordData = ''; if ($this->encryption == self::MS_BIFF_CRYPTO_RC4) { $oldBlock = floor($this->rc4Pos / self::REKEY_BLOCK); $block = (int) floor($pos / self::REKEY_BLOCK); $endBlock = (int) floor(($pos + $len) / self::REKEY_BLOCK); // Spin an RC4 decryptor to the right spot. If we have a decryptor sitting // at a point earlier in the current block, re-use it as we can save some time. if ($block != $oldBlock || $pos < $this->rc4Pos || !$this->rc4Key) { $this->rc4Key = $this->makeKey($block, $this->md5Ctxt); $step = $pos % self::REKEY_BLOCK; } else { $step = $pos - $this->rc4Pos; } $this->rc4Key->RC4(str_repeat("\0", $step)); // Decrypt record data (re-keying at the end of every block) while ($block != $endBlock) { $step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK); $recordData .= $this->rc4Key->RC4(substr($data, 0, $step)); $data = substr($data, $step); $pos += $step; $len -= $step; ++$block; $this->rc4Key = $this->makeKey($block, $this->md5Ctxt); } $recordData .= $this->rc4Key->RC4(substr($data, 0, $len)); // Keep track of the position of this decryptor. // We'll try and re-use it later if we can to speed things up $this->rc4Pos = $pos + $len; } elseif ($this->encryption == self::MS_BIFF_CRYPTO_XOR) { throw new Exception('XOr encryption not supported'); } return $recordData; } /** * Use OLE reader to extract the relevant data streams from the OLE file. * * @param string $filename */ private function loadOLE($filename): void { // OLE reader $ole = new OLERead(); // get excel data, $ole->read($filename); // Get workbook data: workbook stream + sheet streams $this->data = $ole->getStream($ole->wrkbook); // @phpstan-ignore-line // Get summary information data $this->summaryInformation = $ole->getStream($ole->summaryInformation); // Get additional document summary information data $this->documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation); } /** * Read summary information. */ private function readSummaryInformation(): void { if (!isset($this->summaryInformation)) { return; } // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark) // offset: 2; size: 2; // offset: 4; size: 2; OS version // offset: 6; size: 2; OS indicator // offset: 8; size: 16 // offset: 24; size: 4; section count $secCount = self::getInt4d($this->summaryInformation, 24); // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9 // offset: 44; size: 4 $secOffset = self::getInt4d($this->summaryInformation, 44); // section header // offset: $secOffset; size: 4; section length $secLength = self::getInt4d($this->summaryInformation, $secOffset); // offset: $secOffset+4; size: 4; property count $countProperties = self::getInt4d($this->summaryInformation, $secOffset + 4); // initialize code page (used to resolve string values) $codePage = 'CP1252'; // offset: ($secOffset+8); size: var // loop through property decarations and properties for ($i = 0; $i < $countProperties; ++$i) { // offset: ($secOffset+8) + (8 * $i); size: 4; property ID $id = self::getInt4d($this->summaryInformation, ($secOffset + 8) + (8 * $i)); // Use value of property id as appropriate // offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48) $offset = self::getInt4d($this->summaryInformation, ($secOffset + 12) + (8 * $i)); $type = self::getInt4d($this->summaryInformation, $secOffset + $offset); // initialize property value $value = null; // extract property value based on property type switch ($type) { case 0x02: // 2 byte signed integer $value = self::getUInt2d($this->summaryInformation, $secOffset + 4 + $offset); break; case 0x03: // 4 byte signed integer $value = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset); break; case 0x13: // 4 byte unsigned integer // not needed yet, fix later if necessary break; case 0x1E: // null-terminated string prepended by dword string length $byteLength = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset); $value = substr($this->summaryInformation, $secOffset + 8 + $offset, $byteLength); $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage); $value = rtrim($value); break; case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) // PHP-time $value = OLE::OLE2LocalDate(substr($this->summaryInformation, $secOffset + 4 + $offset, 8)); break; case 0x47: // Clipboard format // not needed yet, fix later if necessary break; } switch ($id) { case 0x01: // Code Page $codePage = CodePage::numberToName((int) $value); break; case 0x02: // Title $this->spreadsheet->getProperties()->setTitle("$value"); break; case 0x03: // Subject $this->spreadsheet->getProperties()->setSubject("$value"); break; case 0x04: // Author (Creator) $this->spreadsheet->getProperties()->setCreator("$value"); break; case 0x05: // Keywords $this->spreadsheet->getProperties()->setKeywords("$value"); break; case 0x06: // Comments (Description) $this->spreadsheet->getProperties()->setDescription("$value"); break; case 0x07: // Template // Not supported by PhpSpreadsheet break; case 0x08: // Last Saved By (LastModifiedBy) $this->spreadsheet->getProperties()->setLastModifiedBy("$value"); break; case 0x09: // Revision // Not supported by PhpSpreadsheet break; case 0x0A: // Total Editing Time // Not supported by PhpSpreadsheet break; case 0x0B: // Last Printed // Not supported by PhpSpreadsheet break; case 0x0C: // Created Date/Time $this->spreadsheet->getProperties()->setCreated($value); break; case 0x0D: // Modified Date/Time $this->spreadsheet->getProperties()->setModified($value); break; case 0x0E: // Number of Pages // Not supported by PhpSpreadsheet break; case 0x0F: // Number of Words // Not supported by PhpSpreadsheet break; case 0x10: // Number of Characters // Not supported by PhpSpreadsheet break; case 0x11: // Thumbnail // Not supported by PhpSpreadsheet break; case 0x12: // Name of creating application // Not supported by PhpSpreadsheet break; case 0x13: // Security // Not supported by PhpSpreadsheet break; } } } /** * Read additional document summary information. */ private function readDocumentSummaryInformation(): void { if (!isset($this->documentSummaryInformation)) { return; } // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark) // offset: 2; size: 2; // offset: 4; size: 2; OS version // offset: 6; size: 2; OS indicator // offset: 8; size: 16 // offset: 24; size: 4; section count $secCount = self::getInt4d($this->documentSummaryInformation, 24); // offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae // offset: 44; size: 4; first section offset $secOffset = self::getInt4d($this->documentSummaryInformation, 44); // section header // offset: $secOffset; size: 4; section length $secLength = self::getInt4d($this->documentSummaryInformation, $secOffset); // offset: $secOffset+4; size: 4; property count $countProperties = self::getInt4d($this->documentSummaryInformation, $secOffset + 4); // initialize code page (used to resolve string values) $codePage = 'CP1252'; // offset: ($secOffset+8); size: var // loop through property decarations and properties for ($i = 0; $i < $countProperties; ++$i) { // offset: ($secOffset+8) + (8 * $i); size: 4; property ID $id = self::getInt4d($this->documentSummaryInformation, ($secOffset + 8) + (8 * $i)); // Use value of property id as appropriate // offset: 60 + 8 * $i; size: 4; offset from beginning of section (48) $offset = self::getInt4d($this->documentSummaryInformation, ($secOffset + 12) + (8 * $i)); $type = self::getInt4d($this->documentSummaryInformation, $secOffset + $offset); // initialize property value $value = null; // extract property value based on property type switch ($type) { case 0x02: // 2 byte signed integer $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset); break; case 0x03: // 4 byte signed integer $value = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset); break; case 0x0B: // Boolean $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset); $value = ($value == 0 ? false : true); break; case 0x13: // 4 byte unsigned integer // not needed yet, fix later if necessary break; case 0x1E: // null-terminated string prepended by dword string length $byteLength = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset); $value = substr($this->documentSummaryInformation, $secOffset + 8 + $offset, $byteLength); $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage); $value = rtrim($value); break; case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) // PHP-Time $value = OLE::OLE2LocalDate(substr($this->documentSummaryInformation, $secOffset + 4 + $offset, 8)); break; case 0x47: // Clipboard format // not needed yet, fix later if necessary break; } switch ($id) { case 0x01: // Code Page $codePage = CodePage::numberToName((int) $value); break; case 0x02: // Category $this->spreadsheet->getProperties()->setCategory("$value"); break; case 0x03: // Presentation Target // Not supported by PhpSpreadsheet break; case 0x04: // Bytes // Not supported by PhpSpreadsheet break; case 0x05: // Lines // Not supported by PhpSpreadsheet break; case 0x06: // Paragraphs // Not supported by PhpSpreadsheet break; case 0x07: // Slides // Not supported by PhpSpreadsheet break; case 0x08: // Notes // Not supported by PhpSpreadsheet break; case 0x09: // Hidden Slides // Not supported by PhpSpreadsheet break; case 0x0A: // MM Clips // Not supported by PhpSpreadsheet break; case 0x0B: // Scale Crop // Not supported by PhpSpreadsheet break; case 0x0C: // Heading Pairs // Not supported by PhpSpreadsheet break; case 0x0D: // Titles of Parts // Not supported by PhpSpreadsheet break; case 0x0E: // Manager $this->spreadsheet->getProperties()->setManager("$value"); break; case 0x0F: // Company $this->spreadsheet->getProperties()->setCompany("$value"); break; case 0x10: // Links up-to-date // Not supported by PhpSpreadsheet break; } } } /** * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record. */ private function readDefault(): void { $length = self::getUInt2d($this->data, $this->pos + 2); // move stream pointer to next record $this->pos += 4 + $length; } /** * The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions, * this record stores a note (cell note). This feature was significantly enhanced in Excel 97. */ private function readNote(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->readDataOnly) { return; } $cellAddress = $this->readBIFF8CellAddress(substr($recordData, 0, 4)); if ($this->version == self::XLS_BIFF8) { $noteObjID = self::getUInt2d($recordData, 6); $noteAuthor = self::readUnicodeStringLong(substr($recordData, 8)); $noteAuthor = $noteAuthor['value']; $this->cellNotes[$noteObjID] = [ 'cellRef' => $cellAddress, 'objectID' => $noteObjID, 'author' => $noteAuthor, ]; } else { $extension = false; if ($cellAddress == '$B$65536') { // If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation // note from the previous cell annotation. We're not yet handling this, so annotations longer than the // max 2048 bytes will probably throw a wobbly. $row = self::getUInt2d($recordData, 0); $extension = true; $arrayKeys = array_keys($this->phpSheet->getComments()); $cellAddress = array_pop($arrayKeys); } $cellAddress = str_replace('$', '', (string) $cellAddress); $noteLength = self::getUInt2d($recordData, 4); $noteText = trim(substr($recordData, 6)); if ($extension) { // Concatenate this extension with the currently set comment for the cell $comment = $this->phpSheet->getComment($cellAddress); $commentText = $comment->getText()->getPlainText(); $comment->setText($this->parseRichText($commentText . $noteText)); } else { // Set comment for the cell $this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText)); // ->setAuthor($author) } } } /** * The TEXT Object record contains the text associated with a cell annotation. */ private function readTextObject(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->readDataOnly) { return; } // recordData consists of an array of subrecords looking like this: // grbit: 2 bytes; Option Flags // rot: 2 bytes; rotation // cchText: 2 bytes; length of the text (in the first continue record) // cbRuns: 2 bytes; length of the formatting (in the second continue record) // followed by the continuation records containing the actual text and formatting $grbitOpts = self::getUInt2d($recordData, 0); $rot = self::getUInt2d($recordData, 2); $cchText = self::getUInt2d($recordData, 10); $cbRuns = self::getUInt2d($recordData, 12); $text = $this->getSplicedRecordData(); $textByte = $text['spliceOffsets'][1] - $text['spliceOffsets'][0] - 1; $textStr = substr($text['recordData'], $text['spliceOffsets'][0] + 1, $textByte); // get 1 byte $is16Bit = ord($text['recordData'][0]); // it is possible to use a compressed format, // which omits the high bytes of all characters, if they are all zero if (($is16Bit & 0x01) === 0) { $textStr = StringHelper::ConvertEncoding($textStr, 'UTF-8', 'ISO-8859-1'); } else { $textStr = $this->decodeCodepage($textStr); } $this->textObjects[$this->textObjRef] = [ 'text' => $textStr, 'format' => substr($text['recordData'], $text['spliceOffsets'][1], $cbRuns), 'alignment' => $grbitOpts, 'rotation' => $rot, ]; } /** * Read BOF. */ private function readBof(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = substr($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 2; size: 2; type of the following data $substreamType = self::getUInt2d($recordData, 2); switch ($substreamType) { case self::XLS_WORKBOOKGLOBALS: $version = self::getUInt2d($recordData, 0); if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) { throw new Exception('Cannot read this Excel file. Version is too old.'); } $this->version = $version; break; case self::XLS_WORKSHEET: // do not use this version information for anything // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream break; default: // substream, e.g. chart // just skip the entire substream do { $code = self::getUInt2d($this->data, $this->pos); $this->readDefault(); } while ($code != self::XLS_TYPE_EOF && $this->pos < $this->dataSize); break; } } /** * FILEPASS. * * This record is part of the File Protection Block. It * contains information about the read/write password of the * file. All record contents following this record will be * encrypted. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" * * The decryption functions and objects used from here on in * are based on the source of Spreadsheet-ParseExcel: * https://metacpan.org/release/Spreadsheet-ParseExcel */ private function readFilepass(): void { $length = self::getUInt2d($this->data, $this->pos + 2); if ($length != 54) { throw new Exception('Unexpected file pass record length'); } $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->verifyPassword('VelvetSweatshop', substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) { throw new Exception('Decryption password incorrect'); } $this->encryption = self::MS_BIFF_CRYPTO_RC4; // Decryption required from the record after next onwards $this->encryptionStartPos = $this->pos + self::getUInt2d($this->data, $this->pos + 2); } /** * Make an RC4 decryptor for the given block. * * @param int $block Block for which to create decrypto * @param string $valContext MD5 context state * * @return Xls\RC4 */ private function makeKey($block, $valContext) { $pwarray = str_repeat("\0", 64); for ($i = 0; $i < 5; ++$i) { $pwarray[$i] = $valContext[$i]; } $pwarray[5] = chr($block & 0xff); $pwarray[6] = chr(($block >> 8) & 0xff); $pwarray[7] = chr(($block >> 16) & 0xff); $pwarray[8] = chr(($block >> 24) & 0xff); $pwarray[9] = "\x80"; $pwarray[56] = "\x48"; $md5 = new Xls\MD5(); $md5->add($pwarray); $s = $md5->getContext(); return new Xls\RC4($s); } /** * Verify RC4 file password. * * @param string $password Password to check * @param string $docid Document id * @param string $salt_data Salt data * @param string $hashedsalt_data Hashed salt data * @param string $valContext Set to the MD5 context of the value * * @return bool Success */ private function verifyPassword($password, $docid, $salt_data, $hashedsalt_data, &$valContext) { $pwarray = str_repeat("\0", 64); $iMax = strlen($password); for ($i = 0; $i < $iMax; ++$i) { $o = ord(substr($password, $i, 1)); $pwarray[2 * $i] = chr($o & 0xff); $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff); } $pwarray[2 * $i] = chr(0x80); $pwarray[56] = chr(($i << 4) & 0xff); $md5 = new Xls\MD5(); $md5->add($pwarray); $mdContext1 = $md5->getContext(); $offset = 0; $keyoffset = 0; $tocopy = 5; $md5->reset(); while ($offset != 16) { if ((64 - $offset) < 5) { $tocopy = 64 - $offset; } for ($i = 0; $i <= $tocopy; ++$i) { $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i]; } $offset += $tocopy; if ($offset == 64) { $md5->add($pwarray); $keyoffset = $tocopy; $tocopy = 5 - $tocopy; $offset = 0; continue; } $keyoffset = 0; $tocopy = 5; for ($i = 0; $i < 16; ++$i) { $pwarray[$offset + $i] = $docid[$i]; } $offset += 16; } $pwarray[16] = "\x80"; for ($i = 0; $i < 47; ++$i) { $pwarray[17 + $i] = "\0"; } $pwarray[56] = "\x80"; $pwarray[57] = "\x0a"; $md5->add($pwarray); $valContext = $md5->getContext(); $key = $this->makeKey(0, $valContext); $salt = $key->RC4($salt_data); $hashedsalt = $key->RC4($hashedsalt_data); $salt .= "\x80" . str_repeat("\0", 47); $salt[56] = "\x80"; $md5->reset(); $md5->add($salt); $mdContext2 = $md5->getContext(); return $mdContext2 == $hashedsalt; } /** * CODEPAGE. * * This record stores the text encoding used to write byte * strings, stored as MS Windows code page identifier. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readCodepage(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; code page identifier $codepage = self::getUInt2d($recordData, 0); $this->codepage = CodePage::numberToName($codepage); } /** * DATEMODE. * * This record specifies the base date for displaying date * values. All dates are stored as count of days past this * base date. In BIFF2-BIFF4 this record is part of the * Calculation Settings Block. In BIFF5-BIFF8 it is * stored in the Workbook Globals Substream. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readDateMode(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; 0 = base 1900, 1 = base 1904 Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); if (ord($recordData[0]) == 1) { Date::setExcelCalendar(Date::CALENDAR_MAC_1904); } } /** * Read a FONT record. */ private function readFont(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { $objFont = new Font(); // offset: 0; size: 2; height of the font (in twips = 1/20 of a point) $size = self::getUInt2d($recordData, 0); $objFont->setSize($size / 20); // offset: 2; size: 2; option flags // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8) // bit: 1; mask 0x0002; italic $isItalic = (0x0002 & self::getUInt2d($recordData, 2)) >> 1; if ($isItalic) { $objFont->setItalic(true); } // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8) // bit: 3; mask 0x0008; strikethrough $isStrike = (0x0008 & self::getUInt2d($recordData, 2)) >> 3; if ($isStrike) { $objFont->setStrikethrough(true); } // offset: 4; size: 2; colour index $colorIndex = self::getUInt2d($recordData, 4); $objFont->colorIndex = $colorIndex; // offset: 6; size: 2; font weight $weight = self::getUInt2d($recordData, 6); switch ($weight) { case 0x02BC: $objFont->setBold(true); break; } // offset: 8; size: 2; escapement type $escapement = self::getUInt2d($recordData, 8); CellFont::escapement($objFont, $escapement); // offset: 10; size: 1; underline type $underlineType = ord($recordData[10]); CellFont::underline($objFont, $underlineType); // offset: 11; size: 1; font family // offset: 12; size: 1; character set // offset: 13; size: 1; not used // offset: 14; size: var; font name if ($this->version == self::XLS_BIFF8) { $string = self::readUnicodeStringShort(substr($recordData, 14)); } else { $string = $this->readByteStringShort(substr($recordData, 14)); } $objFont->setName($string['value']); $this->objFonts[] = $objFont; } } /** * FORMAT. * * This record contains information about a number format. * All FORMAT records occur together in a sequential list. * * In BIFF2-BIFF4 other records referencing a FORMAT record * contain a zero-based index into this list. From BIFF5 on * the FORMAT record contains the index itself that will be * used by other records. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readFormat(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { $indexCode = self::getUInt2d($recordData, 0); if ($this->version == self::XLS_BIFF8) { $string = self::readUnicodeStringLong(substr($recordData, 2)); } else { // BIFF7 $string = $this->readByteStringShort(substr($recordData, 2)); } $formatString = $string['value']; // Apache Open Office sets wrong case writing to xls - issue 2239 if ($formatString === 'GENERAL') { $formatString = NumberFormat::FORMAT_GENERAL; } $this->formats[$indexCode] = $formatString; } } /** * XF - Extended Format. * * This record contains formatting information for cells, rows, columns or styles. * According to https://support.microsoft.com/en-us/help/147732 there are always at least 15 cell style XF * and 1 cell XF. * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF * and XF record 15 is a cell XF * We only read the first cell style XF and skip the remaining cell style XF records * We read all cell XF records. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readXf(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; $objStyle = new Style(); if (!$this->readDataOnly) { // offset: 0; size: 2; Index to FONT record if (self::getUInt2d($recordData, 0) < 4) { $fontIndex = self::getUInt2d($recordData, 0); } else { // this has to do with that index 4 is omitted in all BIFF versions for some strange reason // check the OpenOffice documentation of the FONT record $fontIndex = self::getUInt2d($recordData, 0) - 1; } $objStyle->setFont($this->objFonts[$fontIndex]); // offset: 2; size: 2; Index to FORMAT record $numberFormatIndex = self::getUInt2d($recordData, 2); if (isset($this->formats[$numberFormatIndex])) { // then we have user-defined format code $numberFormat = ['formatCode' => $this->formats[$numberFormatIndex]]; } elseif (($code = NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') { // then we have built-in format code $numberFormat = ['formatCode' => $code]; } else { // we set the general format code $numberFormat = ['formatCode' => NumberFormat::FORMAT_GENERAL]; } $objStyle->getNumberFormat()->setFormatCode($numberFormat['formatCode']); // offset: 4; size: 2; XF type, cell protection, and parent style XF // bit 2-0; mask 0x0007; XF_TYPE_PROT $xfTypeProt = self::getUInt2d($recordData, 4); // bit 0; mask 0x01; 1 = cell is locked $isLocked = (0x01 & $xfTypeProt) >> 0; $objStyle->getProtection()->setLocked($isLocked ? Protection::PROTECTION_INHERIT : Protection::PROTECTION_UNPROTECTED); // bit 1; mask 0x02; 1 = Formula is hidden $isHidden = (0x02 & $xfTypeProt) >> 1; $objStyle->getProtection()->setHidden($isHidden ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED); // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF $isCellStyleXf = (0x04 & $xfTypeProt) >> 2; // offset: 6; size: 1; Alignment and text break // bit 2-0, mask 0x07; horizontal alignment $horAlign = (0x07 & ord($recordData[6])) >> 0; Xls\Style\CellAlignment::horizontal($objStyle->getAlignment(), $horAlign); // bit 3, mask 0x08; wrap text $wrapText = (0x08 & ord($recordData[6])) >> 3; Xls\Style\CellAlignment::wrap($objStyle->getAlignment(), $wrapText); // bit 6-4, mask 0x70; vertical alignment $vertAlign = (0x70 & ord($recordData[6])) >> 4; Xls\Style\CellAlignment::vertical($objStyle->getAlignment(), $vertAlign); if ($this->version == self::XLS_BIFF8) { // offset: 7; size: 1; XF_ROTATION: Text rotation angle $angle = ord($recordData[7]); $rotation = 0; if ($angle <= 90) { $rotation = $angle; } elseif ($angle <= 180) { $rotation = 90 - $angle; } elseif ($angle == Alignment::TEXTROTATION_STACK_EXCEL) { $rotation = Alignment::TEXTROTATION_STACK_PHPSPREADSHEET; } $objStyle->getAlignment()->setTextRotation($rotation); // offset: 8; size: 1; Indentation, shrink to cell size, and text direction // bit: 3-0; mask: 0x0F; indent level $indent = (0x0F & ord($recordData[8])) >> 0; $objStyle->getAlignment()->setIndent($indent); // bit: 4; mask: 0x10; 1 = shrink content to fit into cell $shrinkToFit = (0x10 & ord($recordData[8])) >> 4; switch ($shrinkToFit) { case 0: $objStyle->getAlignment()->setShrinkToFit(false); break; case 1: $objStyle->getAlignment()->setShrinkToFit(true); break; } // offset: 9; size: 1; Flags used for attribute groups // offset: 10; size: 4; Cell border lines and background area // bit: 3-0; mask: 0x0000000F; left style if ($bordersLeftStyle = Xls\Style\Border::lookup((0x0000000F & self::getInt4d($recordData, 10)) >> 0)) { $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle); } // bit: 7-4; mask: 0x000000F0; right style if ($bordersRightStyle = Xls\Style\Border::lookup((0x000000F0 & self::getInt4d($recordData, 10)) >> 4)) { $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle); } // bit: 11-8; mask: 0x00000F00; top style if ($bordersTopStyle = Xls\Style\Border::lookup((0x00000F00 & self::getInt4d($recordData, 10)) >> 8)) { $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle); } // bit: 15-12; mask: 0x0000F000; bottom style if ($bordersBottomStyle = Xls\Style\Border::lookup((0x0000F000 & self::getInt4d($recordData, 10)) >> 12)) { $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle); } // bit: 22-16; mask: 0x007F0000; left color $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::getInt4d($recordData, 10)) >> 16; // bit: 29-23; mask: 0x3F800000; right color $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23; // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false; // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right $diagonalUp = ((int) 0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false; if ($diagonalUp === false) { if ($diagonalDown == false) { $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE); } else { $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN); } } elseif ($diagonalDown == false) { $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP); } else { $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH); } // offset: 14; size: 4; // bit: 6-0; mask: 0x0000007F; top color $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0; // bit: 13-7; mask: 0x00003F80; bottom color $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7; // bit: 20-14; mask: 0x001FC000; diagonal color $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14; // bit: 24-21; mask: 0x01E00000; diagonal style if ($bordersDiagonalStyle = Xls\Style\Border::lookup((0x01E00000 & self::getInt4d($recordData, 14)) >> 21)) { $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle); } // bit: 31-26; mask: 0xFC000000 fill pattern if ($fillType = Xls\Style\FillPattern::lookup(((int) 0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) { $objStyle->getFill()->setFillType($fillType); } // offset: 18; size: 2; pattern and background colour // bit: 6-0; mask: 0x007F; color index for pattern color $objStyle->getFill()->startcolorIndex = (0x007F & self::getUInt2d($recordData, 18)) >> 0; // bit: 13-7; mask: 0x3F80; color index for pattern background $objStyle->getFill()->endcolorIndex = (0x3F80 & self::getUInt2d($recordData, 18)) >> 7; } else { // BIFF5 // offset: 7; size: 1; Text orientation and flags $orientationAndFlags = ord($recordData[7]); // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation $xfOrientation = (0x03 & $orientationAndFlags) >> 0; switch ($xfOrientation) { case 0: $objStyle->getAlignment()->setTextRotation(0); break; case 1: $objStyle->getAlignment()->setTextRotation(Alignment::TEXTROTATION_STACK_PHPSPREADSHEET); break; case 2: $objStyle->getAlignment()->setTextRotation(90); break; case 3: $objStyle->getAlignment()->setTextRotation(-90); break; } // offset: 8; size: 4; cell border lines and background area $borderAndBackground = self::getInt4d($recordData, 8); // bit: 6-0; mask: 0x0000007F; color index for pattern color $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0; // bit: 13-7; mask: 0x00003F80; color index for pattern background $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7; // bit: 21-16; mask: 0x003F0000; fill pattern $objStyle->getFill()->setFillType(Xls\Style\FillPattern::lookup((0x003F0000 & $borderAndBackground) >> 16)); // bit: 24-22; mask: 0x01C00000; bottom line style $objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22)); // bit: 31-25; mask: 0xFE000000; bottom line color $objStyle->getBorders()->getBottom()->colorIndex = ((int) 0xFE000000 & $borderAndBackground) >> 25; // offset: 12; size: 4; cell border lines $borderLines = self::getInt4d($recordData, 12); // bit: 2-0; mask: 0x00000007; top line style $objStyle->getBorders()->getTop()->setBorderStyle(Xls\Style\Border::lookup((0x00000007 & $borderLines) >> 0)); // bit: 5-3; mask: 0x00000038; left line style $objStyle->getBorders()->getLeft()->setBorderStyle(Xls\Style\Border::lookup((0x00000038 & $borderLines) >> 3)); // bit: 8-6; mask: 0x000001C0; right line style $objStyle->getBorders()->getRight()->setBorderStyle(Xls\Style\Border::lookup((0x000001C0 & $borderLines) >> 6)); // bit: 15-9; mask: 0x0000FE00; top line color index $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9; // bit: 22-16; mask: 0x007F0000; left line color index $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16; // bit: 29-23; mask: 0x3F800000; right line color index $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23; } // add cellStyleXf or cellXf and update mapping if ($isCellStyleXf) { // we only read one style XF record which is always the first if ($this->xfIndex == 0) { $this->spreadsheet->addCellStyleXf($objStyle); $this->mapCellStyleXfIndex[$this->xfIndex] = 0; } } else { // we read all cell XF records $this->spreadsheet->addCellXf($objStyle); $this->mapCellXfIndex[$this->xfIndex] = count($this->spreadsheet->getCellXfCollection()) - 1; } // update XF index for when we read next record ++$this->xfIndex; } } private function readXfExt(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 2; 0x087D = repeated header // offset: 2; size: 2 // offset: 4; size: 8; not used // offset: 12; size: 2; record version // offset: 14; size: 2; index to XF record which this record modifies $ixfe = self::getUInt2d($recordData, 14); // offset: 16; size: 2; not used // offset: 18; size: 2; number of extension properties that follow $cexts = self::getUInt2d($recordData, 18); // start reading the actual extension data $offset = 20; while ($offset < $length) { // extension type $extType = self::getUInt2d($recordData, $offset); // extension length $cb = self::getUInt2d($recordData, $offset + 2); // extension data $extData = substr($recordData, $offset + 4, $cb); switch ($extType) { case 4: // fill start color $xclfType = self::getUInt2d($extData, 0); // color type $xclrValue = substr($extData, 4, 4); // color value (value based on color type) if ($xclfType == 2) { $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); // modify the relevant style property if (isset($this->mapCellXfIndex[$ixfe])) { $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill(); $fill->getStartColor()->setRGB($rgb); $fill->startcolorIndex = null; // normal color index does not apply, discard } } break; case 5: // fill end color $xclfType = self::getUInt2d($extData, 0); // color type $xclrValue = substr($extData, 4, 4); // color value (value based on color type) if ($xclfType == 2) { $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); // modify the relevant style property if (isset($this->mapCellXfIndex[$ixfe])) { $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill(); $fill->getEndColor()->setRGB($rgb); $fill->endcolorIndex = null; // normal color index does not apply, discard } } break; case 7: // border color top $xclfType = self::getUInt2d($extData, 0); // color type $xclrValue = substr($extData, 4, 4); // color value (value based on color type) if ($xclfType == 2) { $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); // modify the relevant style property if (isset($this->mapCellXfIndex[$ixfe])) { $top = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop(); $top->getColor()->setRGB($rgb); $top->colorIndex = null; // normal color index does not apply, discard } } break; case 8: // border color bottom $xclfType = self::getUInt2d($extData, 0); // color type $xclrValue = substr($extData, 4, 4); // color value (value based on color type) if ($xclfType == 2) { $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); // modify the relevant style property if (isset($this->mapCellXfIndex[$ixfe])) { $bottom = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom(); $bottom->getColor()->setRGB($rgb); $bottom->colorIndex = null; // normal color index does not apply, discard } } break; case 9: // border color left $xclfType = self::getUInt2d($extData, 0); // color type $xclrValue = substr($extData, 4, 4); // color value (value based on color type) if ($xclfType == 2) { $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); // modify the relevant style property if (isset($this->mapCellXfIndex[$ixfe])) { $left = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft(); $left->getColor()->setRGB($rgb); $left->colorIndex = null; // normal color index does not apply, discard } } break; case 10: // border color right $xclfType = self::getUInt2d($extData, 0); // color type $xclrValue = substr($extData, 4, 4); // color value (value based on color type) if ($xclfType == 2) { $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); // modify the relevant style property if (isset($this->mapCellXfIndex[$ixfe])) { $right = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight(); $right->getColor()->setRGB($rgb); $right->colorIndex = null; // normal color index does not apply, discard } } break; case 11: // border color diagonal $xclfType = self::getUInt2d($extData, 0); // color type $xclrValue = substr($extData, 4, 4); // color value (value based on color type) if ($xclfType == 2) { $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); // modify the relevant style property if (isset($this->mapCellXfIndex[$ixfe])) { $diagonal = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal(); $diagonal->getColor()->setRGB($rgb); $diagonal->colorIndex = null; // normal color index does not apply, discard } } break; case 13: // font color $xclfType = self::getUInt2d($extData, 0); // color type $xclrValue = substr($extData, 4, 4); // color value (value based on color type) if ($xclfType == 2) { $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); // modify the relevant style property if (isset($this->mapCellXfIndex[$ixfe])) { $font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont(); $font->getColor()->setRGB($rgb); $font->colorIndex = null; // normal color index does not apply, discard } } break; } $offset += $cb; } } } /** * Read STYLE record. */ private function readStyle(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 2; index to XF record and flag for built-in style $ixfe = self::getUInt2d($recordData, 0); // bit: 11-0; mask 0x0FFF; index to XF record $xfIndex = (0x0FFF & $ixfe) >> 0; // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15); if ($isBuiltIn) { // offset: 2; size: 1; identifier for built-in style $builtInId = ord($recordData[2]); switch ($builtInId) { case 0x00: // currently, we are not using this for anything break; default: break; } } // user-defined; not supported by PhpSpreadsheet } } /** * Read PALETTE record. */ private function readPalette(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 2; number of following colors $nm = self::getUInt2d($recordData, 0); // list of RGB colors for ($i = 0; $i < $nm; ++$i) { $rgb = substr($recordData, 2 + 4 * $i, 4); $this->palette[] = self::readRGB($rgb); } } } /** * SHEET. * * This record is located in the Workbook Globals * Substream and represents a sheet inside the workbook. * One SHEET record is written for each sheet. It stores the * sheet name and a stream offset to the BOF record of the * respective Sheet Substream within the Workbook Stream. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readSheet(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // offset: 0; size: 4; absolute stream position of the BOF record of the sheet // NOTE: not encrypted $rec_offset = self::getInt4d($this->data, $this->pos + 4); // move stream pointer to next record $this->pos += 4 + $length; // offset: 4; size: 1; sheet state switch (ord($recordData[4])) { case 0x00: $sheetState = Worksheet::SHEETSTATE_VISIBLE; break; case 0x01: $sheetState = Worksheet::SHEETSTATE_HIDDEN; break; case 0x02: $sheetState = Worksheet::SHEETSTATE_VERYHIDDEN; break; default: $sheetState = Worksheet::SHEETSTATE_VISIBLE; break; } // offset: 5; size: 1; sheet type $sheetType = ord($recordData[5]); // offset: 6; size: var; sheet name $rec_name = null; if ($this->version == self::XLS_BIFF8) { $string = self::readUnicodeStringShort(substr($recordData, 6)); $rec_name = $string['value']; } elseif ($this->version == self::XLS_BIFF7) { $string = $this->readByteStringShort(substr($recordData, 6)); $rec_name = $string['value']; } $this->sheets[] = [ 'name' => $rec_name, 'offset' => $rec_offset, 'sheetState' => $sheetState, 'sheetType' => $sheetType, ]; } /** * Read EXTERNALBOOK record. */ private function readExternalBook(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset within record data $offset = 0; // there are 4 types of records if (strlen($recordData) > 4) { // external reference // offset: 0; size: 2; number of sheet names ($nm) $nm = self::getUInt2d($recordData, 0); $offset += 2; // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length) $encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2)); $offset += $encodedUrlString['size']; // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length) $externalSheetNames = []; for ($i = 0; $i < $nm; ++$i) { $externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset)); $externalSheetNames[] = $externalSheetNameString['value']; $offset += $externalSheetNameString['size']; } // store the record data $this->externalBooks[] = [ 'type' => 'external', 'encodedUrl' => $encodedUrlString['value'], 'externalSheetNames' => $externalSheetNames, ]; } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) { // internal reference // offset: 0; size: 2; number of sheet in this document // offset: 2; size: 2; 0x01 0x04 $this->externalBooks[] = [ 'type' => 'internal', ]; } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) { // add-in function // offset: 0; size: 2; 0x0001 $this->externalBooks[] = [ 'type' => 'addInFunction', ]; } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) { // DDE links, OLE links // offset: 0; size: 2; 0x0000 // offset: 2; size: var; encoded source document name $this->externalBooks[] = [ 'type' => 'DDEorOLE', ]; } } /** * Read EXTERNNAME record. */ private function readExternName(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // external sheet references provided for named cells if ($this->version == self::XLS_BIFF8) { // offset: 0; size: 2; options $options = self::getUInt2d($recordData, 0); // offset: 2; size: 2; // offset: 4; size: 2; not used // offset: 6; size: var $nameString = self::readUnicodeStringShort(substr($recordData, 6)); // offset: var; size: var; formula data $offset = 6 + $nameString['size']; $formula = $this->getFormulaFromStructure(substr($recordData, $offset)); $this->externalNames[] = [ 'name' => $nameString['value'], 'formula' => $formula, ]; } } /** * Read EXTERNSHEET record. */ private function readExternSheet(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // external sheet references provided for named cells if ($this->version == self::XLS_BIFF8) { // offset: 0; size: 2; number of following ref structures $nm = self::getUInt2d($recordData, 0); for ($i = 0; $i < $nm; ++$i) { $this->ref[] = [ // offset: 2 + 6 * $i; index to EXTERNALBOOK record 'externalBookIndex' => self::getUInt2d($recordData, 2 + 6 * $i), // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record 'firstSheetIndex' => self::getUInt2d($recordData, 4 + 6 * $i), // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record 'lastSheetIndex' => self::getUInt2d($recordData, 6 + 6 * $i), ]; } } } /** * DEFINEDNAME. * * This record is part of a Link Table. It contains the name * and the token array of an internal defined name. Token * arrays of defined names contain tokens with aberrant * token classes. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readDefinedName(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->version == self::XLS_BIFF8) { // retrieves named cells // offset: 0; size: 2; option flags $opts = self::getUInt2d($recordData, 0); // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name $isBuiltInName = (0x0020 & $opts) >> 5; // offset: 2; size: 1; keyboard shortcut // offset: 3; size: 1; length of the name (character count) $nlen = ord($recordData[3]); // offset: 4; size: 2; size of the formula data (it can happen that this is zero) // note: there can also be additional data, this is not included in $flen $flen = self::getUInt2d($recordData, 4); // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based) $scope = self::getUInt2d($recordData, 8); // offset: 14; size: var; Name (Unicode string without length field) $string = self::readUnicodeString(substr($recordData, 14), $nlen); // offset: var; size: $flen; formula data $offset = 14 + $string['size']; $formulaStructure = pack('v', $flen) . substr($recordData, $offset); try { $formula = $this->getFormulaFromStructure($formulaStructure); } catch (PhpSpreadsheetException $e) { $formula = ''; } $this->definedname[] = [ 'isBuiltInName' => $isBuiltInName, 'name' => $string['value'], 'formula' => $formula, 'scope' => $scope, ]; } } /** * Read MSODRAWINGGROUP record. */ private function readMsoDrawingGroup(): void { $length = self::getUInt2d($this->data, $this->pos + 2); // get spliced record data $splicedRecordData = $this->getSplicedRecordData(); $recordData = $splicedRecordData['recordData']; $this->drawingGroupData .= $recordData; } /** * SST - Shared String Table. * * This record contains a list of all strings used anywhere * in the workbook. Each string occurs only once. The * workbook uses indexes into the list to reference the * strings. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readSst(): void { // offset within (spliced) record data $pos = 0; // Limit global SST position, further control for bad SST Length in BIFF8 data $limitposSST = 0; // get spliced record data $splicedRecordData = $this->getSplicedRecordData(); $recordData = $splicedRecordData['recordData']; $spliceOffsets = $splicedRecordData['spliceOffsets']; // offset: 0; size: 4; total number of strings in the workbook $pos += 4; // offset: 4; size: 4; number of following strings ($nm) $nm = self::getInt4d($recordData, 4); $pos += 4; // look up limit position foreach ($spliceOffsets as $spliceOffset) { // it can happen that the string is empty, therefore we need // <= and not just < if ($pos <= $spliceOffset) { $limitposSST = $spliceOffset; } } // loop through the Unicode strings (16-bit length) for ($i = 0; $i < $nm && $pos < $limitposSST; ++$i) { // number of characters in the Unicode string $numChars = self::getUInt2d($recordData, $pos); $pos += 2; // option flags $optionFlags = ord($recordData[$pos]); ++$pos; // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed $isCompressed = (($optionFlags & 0x01) == 0); // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic $hasAsian = (($optionFlags & 0x04) != 0); // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text $hasRichText = (($optionFlags & 0x08) != 0); $formattingRuns = 0; if ($hasRichText) { // number of Rich-Text formatting runs $formattingRuns = self::getUInt2d($recordData, $pos); $pos += 2; } $extendedRunLength = 0; if ($hasAsian) { // size of Asian phonetic setting $extendedRunLength = self::getInt4d($recordData, $pos); $pos += 4; } // expected byte length of character array if not split $len = ($isCompressed) ? $numChars : $numChars * 2; // look up limit position - Check it again to be sure that no error occurs when parsing SST structure $limitpos = null; foreach ($spliceOffsets as $spliceOffset) { // it can happen that the string is empty, therefore we need // <= and not just < if ($pos <= $spliceOffset) { $limitpos = $spliceOffset; break; } } if ($pos + $len <= $limitpos) { // character array is not split between records $retstr = substr($recordData, $pos, $len); $pos += $len; } else { // character array is split between records // first part of character array $retstr = substr($recordData, $pos, $limitpos - $pos); $bytesRead = $limitpos - $pos; // remaining characters in Unicode string $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2)); $pos = $limitpos; // keep reading the characters while ($charsLeft > 0) { // look up next limit position, in case the string span more than one continue record foreach ($spliceOffsets as $spliceOffset) { if ($pos < $spliceOffset) { $limitpos = $spliceOffset; break; } } // repeated option flags // OpenOffice.org documentation 5.21 $option = ord($recordData[$pos]); ++$pos; if ($isCompressed && ($option == 0)) { // 1st fragment compressed // this fragment compressed $len = min($charsLeft, $limitpos - $pos); $retstr .= substr($recordData, $pos, $len); $charsLeft -= $len; $isCompressed = true; } elseif (!$isCompressed && ($option != 0)) { // 1st fragment uncompressed // this fragment uncompressed $len = min($charsLeft * 2, $limitpos - $pos); $retstr .= substr($recordData, $pos, $len); $charsLeft -= $len / 2; $isCompressed = false; } elseif (!$isCompressed && ($option == 0)) { // 1st fragment uncompressed // this fragment compressed $len = min($charsLeft, $limitpos - $pos); for ($j = 0; $j < $len; ++$j) { $retstr .= $recordData[$pos + $j] . chr(0); } $charsLeft -= $len; $isCompressed = false; } else { // 1st fragment compressed // this fragment uncompressed $newstr = ''; $jMax = strlen($retstr); for ($j = 0; $j < $jMax; ++$j) { $newstr .= $retstr[$j] . chr(0); } $retstr = $newstr; $len = min($charsLeft * 2, $limitpos - $pos); $retstr .= substr($recordData, $pos, $len); $charsLeft -= $len / 2; $isCompressed = false; } $pos += $len; } } // convert to UTF-8 $retstr = self::encodeUTF16($retstr, $isCompressed); // read additional Rich-Text information, if any $fmtRuns = []; if ($hasRichText) { // list of formatting runs for ($j = 0; $j < $formattingRuns; ++$j) { // first formatted character; zero-based $charPos = self::getUInt2d($recordData, $pos + $j * 4); // index to font record $fontIndex = self::getUInt2d($recordData, $pos + 2 + $j * 4); $fmtRuns[] = [ 'charPos' => $charPos, 'fontIndex' => $fontIndex, ]; } $pos += 4 * $formattingRuns; } // read additional Asian phonetics information, if any if ($hasAsian) { // For Asian phonetic settings, we skip the extended string data $pos += $extendedRunLength; } // store the shared sting $this->sst[] = [ 'value' => $retstr, 'fmtRuns' => $fmtRuns, ]; } // getSplicedRecordData() takes care of moving current position in data stream } /** * Read PRINTGRIDLINES record. */ private function readPrintGridlines(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines $printGridlines = (bool) self::getUInt2d($recordData, 0); $this->phpSheet->setPrintGridlines($printGridlines); } } /** * Read DEFAULTROWHEIGHT record. */ private function readDefaultRowHeight(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; option flags // offset: 2; size: 2; default height for unused rows, (twips 1/20 point) $height = self::getUInt2d($recordData, 2); $this->phpSheet->getDefaultRowDimension()->setRowHeight($height / 20); } /** * Read SHEETPR record. */ private function readSheetPr(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2 // bit: 6; mask: 0x0040; 0 = outline buttons above outline group $isSummaryBelow = (0x0040 & self::getUInt2d($recordData, 0)) >> 6; $this->phpSheet->setShowSummaryBelow((bool) $isSummaryBelow); // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group $isSummaryRight = (0x0080 & self::getUInt2d($recordData, 0)) >> 7; $this->phpSheet->setShowSummaryRight((bool) $isSummaryRight); // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages // this corresponds to radio button setting in page setup dialog in Excel $this->isFitToPages = (bool) ((0x0100 & self::getUInt2d($recordData, 0)) >> 8); } /** * Read HORIZONTALPAGEBREAKS record. */ private function readHorizontalPageBreaks(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { // offset: 0; size: 2; number of the following row index structures $nm = self::getUInt2d($recordData, 0); // offset: 2; size: 6 * $nm; list of $nm row index structures for ($i = 0; $i < $nm; ++$i) { $r = self::getUInt2d($recordData, 2 + 6 * $i); $cf = self::getUInt2d($recordData, 2 + 6 * $i + 2); $cl = self::getUInt2d($recordData, 2 + 6 * $i + 4); // not sure why two column indexes are necessary? $this->phpSheet->setBreak([$cf + 1, $r], Worksheet::BREAK_ROW); } } } /** * Read VERTICALPAGEBREAKS record. */ private function readVerticalPageBreaks(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { // offset: 0; size: 2; number of the following column index structures $nm = self::getUInt2d($recordData, 0); // offset: 2; size: 6 * $nm; list of $nm row index structures for ($i = 0; $i < $nm; ++$i) { $c = self::getUInt2d($recordData, 2 + 6 * $i); $rf = self::getUInt2d($recordData, 2 + 6 * $i + 2); //$rl = self::getUInt2d($recordData, 2 + 6 * $i + 4); // not sure why two row indexes are necessary? $this->phpSheet->setBreak([$c + 1, ($rf > 0) ? $rf : 1], Worksheet::BREAK_COLUMN); } } } /** * Read HEADER record. */ private function readHeader(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: var // realized that $recordData can be empty even when record exists if ($recordData) { if ($this->version == self::XLS_BIFF8) { $string = self::readUnicodeStringLong($recordData); } else { $string = $this->readByteStringShort($recordData); } $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']); $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']); } } } /** * Read FOOTER record. */ private function readFooter(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: var // realized that $recordData can be empty even when record exists if ($recordData) { if ($this->version == self::XLS_BIFF8) { $string = self::readUnicodeStringLong($recordData); } else { $string = $this->readByteStringShort($recordData); } $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']); $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']); } } } /** * Read HCENTER record. */ private function readHcenter(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally $isHorizontalCentered = (bool) self::getUInt2d($recordData, 0); $this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered); } } /** * Read VCENTER record. */ private function readVcenter(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered $isVerticalCentered = (bool) self::getUInt2d($recordData, 0); $this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered); } } /** * Read LEFTMARGIN record. */ private function readLeftMargin(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 8 $this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData)); } } /** * Read RIGHTMARGIN record. */ private function readRightMargin(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 8 $this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData)); } } /** * Read TOPMARGIN record. */ private function readTopMargin(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 8 $this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData)); } } /** * Read BOTTOMMARGIN record. */ private function readBottomMargin(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 8 $this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData)); } } /** * Read PAGESETUP record. */ private function readPageSetup(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 2; paper size $paperSize = self::getUInt2d($recordData, 0); // offset: 2; size: 2; scaling factor $scale = self::getUInt2d($recordData, 2); // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed $fitToWidth = self::getUInt2d($recordData, 6); // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed $fitToHeight = self::getUInt2d($recordData, 8); // offset: 10; size: 2; option flags // bit: 0; mask: 0x0001; 0=down then over, 1=over then down $isOverThenDown = (0x0001 & self::getUInt2d($recordData, 10)); // bit: 1; mask: 0x0002; 0=landscape, 1=portrait $isPortrait = (0x0002 & self::getUInt2d($recordData, 10)) >> 1; // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init // when this bit is set, do not use flags for those properties $isNotInit = (0x0004 & self::getUInt2d($recordData, 10)) >> 2; if (!$isNotInit) { $this->phpSheet->getPageSetup()->setPaperSize($paperSize); $this->phpSheet->getPageSetup()->setPageOrder(((bool) $isOverThenDown) ? PageSetup::PAGEORDER_OVER_THEN_DOWN : PageSetup::PAGEORDER_DOWN_THEN_OVER); $this->phpSheet->getPageSetup()->setOrientation(((bool) $isPortrait) ? PageSetup::ORIENTATION_PORTRAIT : PageSetup::ORIENTATION_LANDSCAPE); $this->phpSheet->getPageSetup()->setScale($scale, false); $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages); $this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false); $this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false); } // offset: 16; size: 8; header margin (IEEE 754 floating-point value) $marginHeader = self::extractNumber(substr($recordData, 16, 8)); $this->phpSheet->getPageMargins()->setHeader($marginHeader); // offset: 24; size: 8; footer margin (IEEE 754 floating-point value) $marginFooter = self::extractNumber(substr($recordData, 24, 8)); $this->phpSheet->getPageMargins()->setFooter($marginFooter); } } /** * PROTECT - Sheet protection (BIFF2 through BIFF8) * if this record is omitted, then it also means no sheet protection. */ private function readProtect(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->readDataOnly) { return; } // offset: 0; size: 2; // bit 0, mask 0x01; 1 = sheet is protected $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0; $this->phpSheet->getProtection()->setSheet((bool) $bool); } /** * SCENPROTECT. */ private function readScenProtect(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->readDataOnly) { return; } // offset: 0; size: 2; // bit: 0, mask 0x01; 1 = scenarios are protected $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0; $this->phpSheet->getProtection()->setScenarios((bool) $bool); } /** * OBJECTPROTECT. */ private function readObjectProtect(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->readDataOnly) { return; } // offset: 0; size: 2; // bit: 0, mask 0x01; 1 = objects are protected $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0; $this->phpSheet->getProtection()->setObjects((bool) $bool); } /** * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8). */ private function readPassword(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 2; 16-bit hash value of password $password = strtoupper(dechex(self::getUInt2d($recordData, 0))); // the hashed password $this->phpSheet->getProtection()->setPassword($password, true); } } /** * Read DEFCOLWIDTH record. */ private function readDefColWidth(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; default column width $width = self::getUInt2d($recordData, 0); if ($width != 8) { $this->phpSheet->getDefaultColumnDimension()->setWidth($width); } } /** * Read COLINFO record. */ private function readColInfo(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 2; index to first column in range $firstColumnIndex = self::getUInt2d($recordData, 0); // offset: 2; size: 2; index to last column in range $lastColumnIndex = self::getUInt2d($recordData, 2); // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character $width = self::getUInt2d($recordData, 4); // offset: 6; size: 2; index to XF record for default column formatting $xfIndex = self::getUInt2d($recordData, 6); // offset: 8; size: 2; option flags // bit: 0; mask: 0x0001; 1= columns are hidden $isHidden = (0x0001 & self::getUInt2d($recordData, 8)) >> 0; // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline) $level = (0x0700 & self::getUInt2d($recordData, 8)) >> 8; // bit: 12; mask: 0x1000; 1 = collapsed $isCollapsed = (bool) ((0x1000 & self::getUInt2d($recordData, 8)) >> 12); // offset: 10; size: 2; not used for ($i = $firstColumnIndex + 1; $i <= $lastColumnIndex + 1; ++$i) { if ($lastColumnIndex == 255 || $lastColumnIndex == 256) { $this->phpSheet->getDefaultColumnDimension()->setWidth($width / 256); break; } $this->phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256); $this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden); $this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level); $this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed); if (isset($this->mapCellXfIndex[$xfIndex])) { $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]); } } } } /** * ROW. * * This record contains the properties of a single row in a * sheet. Rows and cells in a sheet are divided into blocks * of 32 rows. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readRow(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 2; index of this row $r = self::getUInt2d($recordData, 0); // offset: 2; size: 2; index to column of the first cell which is described by a cell record // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1 // offset: 6; size: 2; // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point $height = (0x7FFF & self::getUInt2d($recordData, 6)) >> 0; // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height $useDefaultHeight = (0x8000 & self::getUInt2d($recordData, 6)) >> 15; if (!$useDefaultHeight) { $this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20); } // offset: 8; size: 2; not used // offset: 10; size: 2; not used in BIFF5-BIFF8 // offset: 12; size: 4; option flags and default row formatting // bit: 2-0: mask: 0x00000007; outline level of the row $level = (0x00000007 & self::getInt4d($recordData, 12)) >> 0; $this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level); // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed $isCollapsed = (bool) ((0x00000010 & self::getInt4d($recordData, 12)) >> 4); $this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed); // bit: 5; mask: 0x00000020; 1 = row is hidden $isHidden = (0x00000020 & self::getInt4d($recordData, 12)) >> 5; $this->phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden); // bit: 7; mask: 0x00000080; 1 = row has explicit format $hasExplicitFormat = (0x00000080 & self::getInt4d($recordData, 12)) >> 7; // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record $xfIndex = (0x0FFF0000 & self::getInt4d($recordData, 12)) >> 16; if ($hasExplicitFormat && isset($this->mapCellXfIndex[$xfIndex])) { $this->phpSheet->getRowDimension($r + 1)->setXfIndex($this->mapCellXfIndex[$xfIndex]); } } } /** * Read RK record * This record represents a cell that contains an RK value * (encoded integer or floating-point value). If a * floating-point value cannot be encoded to an RK value, * a NUMBER record will be written. This record replaces the * record INTEGER written in BIFF2. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readRk(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; index to row $row = self::getUInt2d($recordData, 0); // offset: 2; size: 2; index to column $column = self::getUInt2d($recordData, 2); $columnString = Coordinate::stringFromColumnIndex($column + 1); // Read cell? if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { // offset: 4; size: 2; index to XF record $xfIndex = self::getUInt2d($recordData, 4); // offset: 6; size: 4; RK value $rknum = self::getInt4d($recordData, 6); $numValue = self::getIEEE754($rknum); $cell = $this->phpSheet->getCell($columnString . ($row + 1)); if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add style information $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } // add cell $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC); } } /** * Read LABELSST record * This record represents a cell that contains a string. It * replaces the LABEL record and RSTRING record used in * BIFF2-BIFF5. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readLabelSst(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; index to row $row = self::getUInt2d($recordData, 0); // offset: 2; size: 2; index to column $column = self::getUInt2d($recordData, 2); $columnString = Coordinate::stringFromColumnIndex($column + 1); $emptyCell = true; // Read cell? if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { // offset: 4; size: 2; index to XF record $xfIndex = self::getUInt2d($recordData, 4); // offset: 6; size: 4; index to SST record $index = self::getInt4d($recordData, 6); // add cell if (($fmtRuns = $this->sst[$index]['fmtRuns']) && !$this->readDataOnly) { // then we should treat as rich text $richText = new RichText(); $charPos = 0; $sstCount = count($this->sst[$index]['fmtRuns']); for ($i = 0; $i <= $sstCount; ++$i) { if (isset($fmtRuns[$i])) { $text = StringHelper::substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos); $charPos = $fmtRuns[$i]['charPos']; } else { $text = StringHelper::substring($this->sst[$index]['value'], $charPos, StringHelper::countCharacters($this->sst[$index]['value'])); } if (StringHelper::countCharacters($text) > 0) { if ($i == 0) { // first text run, no style $richText->createText($text); } else { $textRun = $richText->createTextRun($text); if (isset($fmtRuns[$i - 1])) { if ($fmtRuns[$i - 1]['fontIndex'] < 4) { $fontIndex = $fmtRuns[$i - 1]['fontIndex']; } else { // this has to do with that index 4 is omitted in all BIFF versions for some stra nge reason // check the OpenOffice documentation of the FONT record $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1; } if (array_key_exists($fontIndex, $this->objFonts) === false) { $fontIndex = count($this->objFonts) - 1; } $textRun->setFont(clone $this->objFonts[$fontIndex]); } } } } if ($this->readEmptyCells || trim($richText->getPlainText()) !== '') { $cell = $this->phpSheet->getCell($columnString . ($row + 1)); $cell->setValueExplicit($richText, DataType::TYPE_STRING); $emptyCell = false; } } else { if ($this->readEmptyCells || trim($this->sst[$index]['value']) !== '') { $cell = $this->phpSheet->getCell($columnString . ($row + 1)); $cell->setValueExplicit($this->sst[$index]['value'], DataType::TYPE_STRING); $emptyCell = false; } } if (!$this->readDataOnly && !$emptyCell && isset($this->mapCellXfIndex[$xfIndex])) { // add style information $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } } } /** * Read MULRK record * This record represents a cell range containing RK value * cells. All cells are located in the same row. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readMulRk(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; index to row $row = self::getUInt2d($recordData, 0); // offset: 2; size: 2; index to first column $colFirst = self::getUInt2d($recordData, 2); // offset: var; size: 2; index to last column $colLast = self::getUInt2d($recordData, $length - 2); $columns = $colLast - $colFirst + 1; // offset within record data $offset = 4; for ($i = 1; $i <= $columns; ++$i) { $columnString = Coordinate::stringFromColumnIndex($colFirst + $i); // Read cell? if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { // offset: var; size: 2; index to XF record $xfIndex = self::getUInt2d($recordData, $offset); // offset: var; size: 4; RK value $numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2)); $cell = $this->phpSheet->getCell($columnString . ($row + 1)); if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add style $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } // add cell value $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC); } $offset += 6; } } /** * Read NUMBER record * This record represents a cell that contains a * floating-point value. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readNumber(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; index to row $row = self::getUInt2d($recordData, 0); // offset: 2; size 2; index to column $column = self::getUInt2d($recordData, 2); $columnString = Coordinate::stringFromColumnIndex($column + 1); // Read cell? if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { // offset 4; size: 2; index to XF record $xfIndex = self::getUInt2d($recordData, 4); $numValue = self::extractNumber(substr($recordData, 6, 8)); $cell = $this->phpSheet->getCell($columnString . ($row + 1)); if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add cell style $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } // add cell value $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC); } } /** * Read FORMULA record + perhaps a following STRING record if formula result is a string * This record contains the token array and the result of a * formula cell. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readFormula(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; row index $row = self::getUInt2d($recordData, 0); // offset: 2; size: 2; col index $column = self::getUInt2d($recordData, 2); $columnString = Coordinate::stringFromColumnIndex($column + 1); // offset: 20: size: variable; formula structure $formulaStructure = substr($recordData, 20); // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc. $options = self::getUInt2d($recordData, 14); // bit: 0; mask: 0x0001; 1 = recalculate always // bit: 1; mask: 0x0002; 1 = calculate on open // bit: 2; mask: 0x0008; 1 = part of a shared formula $isPartOfSharedFormula = (bool) (0x0008 & $options); // WARNING: // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true // the formula data may be ordinary formula data, therefore we need to check // explicitly for the tExp token (0x01) $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure[2]) == 0x01; if ($isPartOfSharedFormula) { // part of shared formula which means there will be a formula with a tExp token and nothing else // get the base cell, grab tExp token $baseRow = self::getUInt2d($formulaStructure, 3); $baseCol = self::getUInt2d($formulaStructure, 5); $this->baseCell = Coordinate::stringFromColumnIndex($baseCol + 1) . ($baseRow + 1); } // Read cell? if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { if ($isPartOfSharedFormula) { // formula is added to this cell after the sheet has been read $this->sharedFormulaParts[$columnString . ($row + 1)] = $this->baseCell; } // offset: 16: size: 4; not used // offset: 4; size: 2; XF index $xfIndex = self::getUInt2d($recordData, 4); // offset: 6; size: 8; result of the formula if ((ord($recordData[6]) == 0) && (ord($recordData[12]) == 255) && (ord($recordData[13]) == 255)) { // String formula. Result follows in appended STRING record $dataType = DataType::TYPE_STRING; // read possible SHAREDFMLA record $code = self::getUInt2d($this->data, $this->pos); if ($code == self::XLS_TYPE_SHAREDFMLA) { $this->readSharedFmla(); } // read STRING record $value = $this->readString(); } elseif ( (ord($recordData[6]) == 1) && (ord($recordData[12]) == 255) && (ord($recordData[13]) == 255) ) { // Boolean formula. Result is in +2; 0=false, 1=true $dataType = DataType::TYPE_BOOL; $value = (bool) ord($recordData[8]); } elseif ( (ord($recordData[6]) == 2) && (ord($recordData[12]) == 255) && (ord($recordData[13]) == 255) ) { // Error formula. Error code is in +2 $dataType = DataType::TYPE_ERROR; $value = Xls\ErrorCode::lookup(ord($recordData[8])); } elseif ( (ord($recordData[6]) == 3) && (ord($recordData[12]) == 255) && (ord($recordData[13]) == 255) ) { // Formula result is a null string $dataType = DataType::TYPE_NULL; $value = ''; } else { // forumla result is a number, first 14 bytes like _NUMBER record $dataType = DataType::TYPE_NUMERIC; $value = self::extractNumber(substr($recordData, 6, 8)); } $cell = $this->phpSheet->getCell($columnString . ($row + 1)); if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add cell style $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } // store the formula if (!$isPartOfSharedFormula) { // not part of shared formula // add cell value. If we can read formula, populate with formula, otherwise just used cached value try { if ($this->version != self::XLS_BIFF8) { throw new Exception('Not BIFF8. Can only read BIFF8 formulas'); } $formula = $this->getFormulaFromStructure($formulaStructure); // get formula in human language $cell->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA); } catch (PhpSpreadsheetException $e) { $cell->setValueExplicit($value, $dataType); } } else { if ($this->version == self::XLS_BIFF8) { // do nothing at this point, formula id added later in the code } else { $cell->setValueExplicit($value, $dataType); } } // store the cached calculated value $cell->setCalculatedValue($value); } } /** * Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader, * which usually contains relative references. * These will be used to construct the formula in each shared formula part after the sheet is read. */ private function readSharedFmla(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything $cellRange = substr($recordData, 0, 6); $cellRange = $this->readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax // offset: 6, size: 1; not used // offset: 7, size: 1; number of existing FORMULA records for this shared formula $no = ord($recordData[7]); // offset: 8, size: var; Binary token array of the shared formula $formula = substr($recordData, 8); // at this point we only store the shared formula for later use $this->sharedFormulas[$this->baseCell] = $formula; } /** * Read a STRING record from current stream position and advance the stream pointer to next record * This record is used for storing result from FORMULA record when it is a string, and * it occurs directly after the FORMULA record. * * @return string The string contents as UTF-8 */ private function readString() { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->version == self::XLS_BIFF8) { $string = self::readUnicodeStringLong($recordData); $value = $string['value']; } else { $string = $this->readByteStringLong($recordData); $value = $string['value']; } return $value; } /** * Read BOOLERR record * This record represents a Boolean value or error value * cell. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readBoolErr(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; row index $row = self::getUInt2d($recordData, 0); // offset: 2; size: 2; column index $column = self::getUInt2d($recordData, 2); $columnString = Coordinate::stringFromColumnIndex($column + 1); // Read cell? if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { // offset: 4; size: 2; index to XF record $xfIndex = self::getUInt2d($recordData, 4); // offset: 6; size: 1; the boolean value or error value $boolErr = ord($recordData[6]); // offset: 7; size: 1; 0=boolean; 1=error $isError = ord($recordData[7]); $cell = $this->phpSheet->getCell($columnString . ($row + 1)); switch ($isError) { case 0: // boolean $value = (bool) $boolErr; // add cell value $cell->setValueExplicit($value, DataType::TYPE_BOOL); break; case 1: // error type $value = Xls\ErrorCode::lookup($boolErr); // add cell value $cell->setValueExplicit($value, DataType::TYPE_ERROR); break; } if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add cell style $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } } } /** * Read MULBLANK record * This record represents a cell range of empty cells. All * cells are located in the same row. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readMulBlank(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; index to row $row = self::getUInt2d($recordData, 0); // offset: 2; size: 2; index to first column $fc = self::getUInt2d($recordData, 2); // offset: 4; size: 2 x nc; list of indexes to XF records // add style information if (!$this->readDataOnly && $this->readEmptyCells) { for ($i = 0; $i < $length / 2 - 3; ++$i) { $columnString = Coordinate::stringFromColumnIndex($fc + $i + 1); // Read cell? if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { $xfIndex = self::getUInt2d($recordData, 4 + 2 * $i); if (isset($this->mapCellXfIndex[$xfIndex])) { $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]); } } } } // offset: 6; size 2; index to last column (not needed) } /** * Read LABEL record * This record represents a cell that contains a string. In * BIFF8 it is usually replaced by the LABELSST record. * Excel still uses this record, if it copies unformatted * text cells to the clipboard. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readLabel(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; index to row $row = self::getUInt2d($recordData, 0); // offset: 2; size: 2; index to column $column = self::getUInt2d($recordData, 2); $columnString = Coordinate::stringFromColumnIndex($column + 1); // Read cell? if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { // offset: 4; size: 2; XF index $xfIndex = self::getUInt2d($recordData, 4); // add cell value // todo: what if string is very long? continue record if ($this->version == self::XLS_BIFF8) { $string = self::readUnicodeStringLong(substr($recordData, 6)); $value = $string['value']; } else { $string = $this->readByteStringLong(substr($recordData, 6)); $value = $string['value']; } if ($this->readEmptyCells || trim($value) !== '') { $cell = $this->phpSheet->getCell($columnString . ($row + 1)); $cell->setValueExplicit($value, DataType::TYPE_STRING); if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add cell style $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } } } } /** * Read BLANK record. */ private function readBlank(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; row index $row = self::getUInt2d($recordData, 0); // offset: 2; size: 2; col index $col = self::getUInt2d($recordData, 2); $columnString = Coordinate::stringFromColumnIndex($col + 1); // Read cell? if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { // offset: 4; size: 2; XF index $xfIndex = self::getUInt2d($recordData, 4); // add style information if (!$this->readDataOnly && $this->readEmptyCells && isset($this->mapCellXfIndex[$xfIndex])) { $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]); } } } /** * Read MSODRAWING record. */ private function readMsoDrawing(): void { $length = self::getUInt2d($this->data, $this->pos + 2); // get spliced record data $splicedRecordData = $this->getSplicedRecordData(); $recordData = $splicedRecordData['recordData']; $this->drawingData .= $recordData; } /** * Read OBJ record. */ private function readObj(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->readDataOnly || $this->version != self::XLS_BIFF8) { return; } // recordData consists of an array of subrecords looking like this: // ft: 2 bytes; ftCmo type (0x15) // cb: 2 bytes; size in bytes of ftCmo data // ot: 2 bytes; Object Type // id: 2 bytes; Object id number // grbit: 2 bytes; Option Flags // data: var; subrecord data // for now, we are just interested in the second subrecord containing the object type $ftCmoType = self::getUInt2d($recordData, 0); $cbCmoSize = self::getUInt2d($recordData, 2); $otObjType = self::getUInt2d($recordData, 4); $idObjID = self::getUInt2d($recordData, 6); $grbitOpts = self::getUInt2d($recordData, 6); $this->objs[] = [ 'ftCmoType' => $ftCmoType, 'cbCmoSize' => $cbCmoSize, 'otObjType' => $otObjType, 'idObjID' => $idObjID, 'grbitOpts' => $grbitOpts, ]; $this->textObjRef = $idObjID; } /** * Read WINDOW2 record. */ private function readWindow2(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; option flags $options = self::getUInt2d($recordData, 0); // offset: 2; size: 2; index to first visible row $firstVisibleRow = self::getUInt2d($recordData, 2); // offset: 4; size: 2; index to first visible colum $firstVisibleColumn = self::getUInt2d($recordData, 4); $zoomscaleInPageBreakPreview = 0; $zoomscaleInNormalView = 0; if ($this->version === self::XLS_BIFF8) { // offset: 8; size: 2; not used // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%) // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%) // offset: 14; size: 4; not used if (!isset($recordData[10])) { $zoomscaleInPageBreakPreview = 0; } else { $zoomscaleInPageBreakPreview = self::getUInt2d($recordData, 10); } if ($zoomscaleInPageBreakPreview === 0) { $zoomscaleInPageBreakPreview = 60; } if (!isset($recordData[12])) { $zoomscaleInNormalView = 0; } else { $zoomscaleInNormalView = self::getUInt2d($recordData, 12); } if ($zoomscaleInNormalView === 0) { $zoomscaleInNormalView = 100; } } // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines $showGridlines = (bool) ((0x0002 & $options) >> 1); $this->phpSheet->setShowGridlines($showGridlines); // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers $showRowColHeaders = (bool) ((0x0004 & $options) >> 2); $this->phpSheet->setShowRowColHeaders($showRowColHeaders); // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen $this->frozen = (bool) ((0x0008 & $options) >> 3); // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left $this->phpSheet->setRightToLeft((bool) ((0x0040 & $options) >> 6)); // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active $isActive = (bool) ((0x0400 & $options) >> 10); if ($isActive) { $this->spreadsheet->setActiveSheetIndex($this->spreadsheet->getIndex($this->phpSheet)); } // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11); //FIXME: set $firstVisibleRow and $firstVisibleColumn if ($this->phpSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_PAGE_LAYOUT) { //NOTE: this setting is inferior to page layout view(Excel2007-) $view = $isPageBreakPreview ? SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : SheetView::SHEETVIEW_NORMAL; $this->phpSheet->getSheetView()->setView($view); if ($this->version === self::XLS_BIFF8) { $zoomScale = $isPageBreakPreview ? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView; $this->phpSheet->getSheetView()->setZoomScale($zoomScale); $this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView); } } } /** * Read PLV Record(Created by Excel2007 or upper). */ private function readPageLayoutView(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; rt //->ignore $rt = self::getUInt2d($recordData, 0); // offset: 2; size: 2; grbitfr //->ignore $grbitFrt = self::getUInt2d($recordData, 2); // offset: 4; size: 8; reserved //->ignore // offset: 12; size 2; zoom scale $wScalePLV = self::getUInt2d($recordData, 12); // offset: 14; size 2; grbit $grbit = self::getUInt2d($recordData, 14); // decomprise grbit $fPageLayoutView = $grbit & 0x01; $fRulerVisible = ($grbit >> 1) & 0x01; //no support $fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support if ($fPageLayoutView === 1) { $this->phpSheet->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_LAYOUT); $this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT } //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW. } /** * Read SCL record. */ private function readScl(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // offset: 0; size: 2; numerator of the view magnification $numerator = self::getUInt2d($recordData, 0); // offset: 2; size: 2; numerator of the view magnification $denumerator = self::getUInt2d($recordData, 2); // set the zoom scale (in percent) $this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator); } /** * Read PANE record. */ private function readPane(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 2; position of vertical split $px = self::getUInt2d($recordData, 0); // offset: 2; size: 2; position of horizontal split $py = self::getUInt2d($recordData, 2); // offset: 4; size: 2; top most visible row in the bottom pane $rwTop = self::getUInt2d($recordData, 4); // offset: 6; size: 2; first visible left column in the right pane $colLeft = self::getUInt2d($recordData, 6); if ($this->frozen) { // frozen panes $cell = Coordinate::stringFromColumnIndex($px + 1) . ($py + 1); $topLeftCell = Coordinate::stringFromColumnIndex($colLeft + 1) . ($rwTop + 1); $this->phpSheet->freezePane($cell, $topLeftCell); } // unfrozen panes; split windows; not supported by PhpSpreadsheet core } } /** * Read SELECTION record. There is one such record for each pane in the sheet. */ private function readSelection(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 1; pane identifier $paneId = ord($recordData[0]); // offset: 1; size: 2; index to row of the active cell $r = self::getUInt2d($recordData, 1); // offset: 3; size: 2; index to column of the active cell $c = self::getUInt2d($recordData, 3); // offset: 5; size: 2; index into the following cell range list to the // entry that contains the active cell $index = self::getUInt2d($recordData, 5); // offset: 7; size: var; cell range address list containing all selected cell ranges $data = substr($recordData, 7); $cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0]; // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!) if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) { $selectedCells = (string) preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells); } // first row '1' + last row '65536' indicates that full column is selected if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) { $selectedCells = (string) preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells); } // first column 'A' + last column 'IV' indicates that full row is selected if (preg_match('/^(A\d+\:)IV(\d+)$/', $selectedCells)) { $selectedCells = (string) preg_replace('/^(A\d+\:)IV(\d+)$/', '${1}XFD${2}', $selectedCells); } $this->phpSheet->setSelectedCells($selectedCells); } } private function includeCellRangeFiltered(string $cellRangeAddress): bool { $includeCellRange = true; if ($this->getReadFilter() !== null) { $includeCellRange = false; $rangeBoundaries = Coordinate::getRangeBoundaries($cellRangeAddress); ++$rangeBoundaries[1][0]; for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) { for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) { if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) { $includeCellRange = true; break 2; } } } } return $includeCellRange; } /** * MERGEDCELLS. * * This record contains the addresses of merged cell ranges * in the current sheet. * * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ private function readMergedCells(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData); foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) { if ( (strpos($cellRangeAddress, ':') !== false) && ($this->includeCellRangeFiltered($cellRangeAddress)) ) { $this->phpSheet->mergeCells($cellRangeAddress, Worksheet::MERGE_CELL_CONTENT_HIDE); } } } } /** * Read HYPERLINK record. */ private function readHyperLink(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer forward to next record $this->pos += 4 + $length; if (!$this->readDataOnly) { // offset: 0; size: 8; cell range address of all cells containing this hyperlink try { $cellRange = $this->readBIFF8CellRangeAddressFixed($recordData); } catch (PhpSpreadsheetException $e) { return; } // offset: 8, size: 16; GUID of StdLink // offset: 24, size: 4; unknown value // offset: 28, size: 4; option flags // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL $isFileLinkOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 0; // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL $isAbsPathOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 1; // bit: 2 (and 4); mask: 0x00000014; 0 = no description $hasDesc = (0x00000014 & self::getUInt2d($recordData, 28)) >> 2; // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text $hasText = (0x00000008 & self::getUInt2d($recordData, 28)) >> 3; // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame $hasFrame = (0x00000080 & self::getUInt2d($recordData, 28)) >> 7; // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name) $isUNC = (0x00000100 & self::getUInt2d($recordData, 28)) >> 8; // offset within record data $offset = 32; if ($hasDesc) { // offset: 32; size: var; character count of description text $dl = self::getInt4d($recordData, 32); // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated $desc = self::encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false); $offset += 4 + 2 * $dl; } if ($hasFrame) { $fl = self::getInt4d($recordData, $offset); $offset += 4 + 2 * $fl; } // detect type of hyperlink (there are 4 types) $hyperlinkType = null; if ($isUNC) { $hyperlinkType = 'UNC'; } elseif (!$isFileLinkOrUrl) { $hyperlinkType = 'workbook'; } elseif (ord($recordData[$offset]) == 0x03) { $hyperlinkType = 'local'; } elseif (ord($recordData[$offset]) == 0xE0) { $hyperlinkType = 'URL'; } switch ($hyperlinkType) { case 'URL': // section 5.58.2: Hyperlink containing a URL // e.g. http://example.org/index.php // offset: var; size: 16; GUID of URL Moniker $offset += 16; // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word $us = self::getInt4d($recordData, $offset); $offset += 4; // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated $url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false); $nullOffset = strpos($url, chr(0x00)); if ($nullOffset) { $url = substr($url, 0, $nullOffset); } $url .= $hasText ? '#' : ''; $offset += $us; break; case 'local': // section 5.58.3: Hyperlink to local file // examples: // mydoc.txt // ../../somedoc.xls#Sheet!A1 // offset: var; size: 16; GUI of File Moniker $offset += 16; // offset: var; size: 2; directory up-level count. $upLevelCount = self::getUInt2d($recordData, $offset); $offset += 2; // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word $sl = self::getInt4d($recordData, $offset); $offset += 4; // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string) $shortenedFilePath = substr($recordData, $offset, $sl); $shortenedFilePath = self::encodeUTF16($shortenedFilePath, true); $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero $offset += $sl; // offset: var; size: 24; unknown sequence $offset += 24; // extended file path // offset: var; size: 4; size of the following file link field including string lenth mark $sz = self::getInt4d($recordData, $offset); $offset += 4; // only present if $sz > 0 if ($sz > 0) { // offset: var; size: 4; size of the character array of the extended file path and name $xl = self::getInt4d($recordData, $offset); $offset += 4; // offset: var; size 2; unknown $offset += 2; // offset: var; size $xl; character array of the extended file path and name. $extendedFilePath = substr($recordData, $offset, $xl); $extendedFilePath = self::encodeUTF16($extendedFilePath, false); $offset += $xl; } // construct the path $url = str_repeat('..\\', $upLevelCount); $url .= ($sz > 0) ? $extendedFilePath : $shortenedFilePath; // use extended path if available $url .= $hasText ? '#' : ''; break; case 'UNC': // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path // todo: implement return; case 'workbook': // section 5.58.5: Hyperlink to the Current Workbook // e.g. Sheet2!B1:C2, stored in text mark field $url = 'sheet://'; break; default: return; } if ($hasText) { // offset: var; size: 4; character count of text mark including trailing zero word $tl = self::getInt4d($recordData, $offset); $offset += 4; // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated $text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false); $url .= $text; } // apply the hyperlink to all the relevant cells foreach (Coordinate::extractAllCellReferencesInRange($cellRange) as $coordinate) { $this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url); } } } /** * Read DATAVALIDATIONS record. */ private function readDataValidations(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer forward to next record $this->pos += 4 + $length; } /** * Read DATAVALIDATION record. */ private function readDataValidation(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer forward to next record $this->pos += 4 + $length; if ($this->readDataOnly) { return; } // offset: 0; size: 4; Options $options = self::getInt4d($recordData, 0); // bit: 0-3; mask: 0x0000000F; type $type = (0x0000000F & $options) >> 0; $type = Xls\DataValidationHelper::type($type); // bit: 4-6; mask: 0x00000070; error type $errorStyle = (0x00000070 & $options) >> 4; $errorStyle = Xls\DataValidationHelper::errorStyle($errorStyle); // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list) // I have only seen cases where this is 1 $explicitFormula = (0x00000080 & $options) >> 7; // bit: 8; mask: 0x00000100; 1= empty cells allowed $allowBlank = (0x00000100 & $options) >> 8; // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity $suppressDropDown = (0x00000200 & $options) >> 9; // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected $showInputMessage = (0x00040000 & $options) >> 18; // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered $showErrorMessage = (0x00080000 & $options) >> 19; // bit: 20-23; mask: 0x00F00000; condition operator $operator = (0x00F00000 & $options) >> 20; $operator = Xls\DataValidationHelper::operator($operator); if ($type === null || $errorStyle === null || $operator === null) { return; } // offset: 4; size: var; title of the prompt box $offset = 4; $string = self::readUnicodeStringLong(substr($recordData, $offset)); $promptTitle = $string['value'] !== chr(0) ? $string['value'] : ''; $offset += $string['size']; // offset: var; size: var; title of the error box $string = self::readUnicodeStringLong(substr($recordData, $offset)); $errorTitle = $string['value'] !== chr(0) ? $string['value'] : ''; $offset += $string['size']; // offset: var; size: var; text of the prompt box $string = self::readUnicodeStringLong(substr($recordData, $offset)); $prompt = $string['value'] !== chr(0) ? $string['value'] : ''; $offset += $string['size']; // offset: var; size: var; text of the error box $string = self::readUnicodeStringLong(substr($recordData, $offset)); $error = $string['value'] !== chr(0) ? $string['value'] : ''; $offset += $string['size']; // offset: var; size: 2; size of the formula data for the first condition $sz1 = self::getUInt2d($recordData, $offset); $offset += 2; // offset: var; size: 2; not used $offset += 2; // offset: var; size: $sz1; formula data for first condition (without size field) $formula1 = substr($recordData, $offset, $sz1); $formula1 = pack('v', $sz1) . $formula1; // prepend the length try { $formula1 = $this->getFormulaFromStructure($formula1); // in list type validity, null characters are used as item separators if ($type == DataValidation::TYPE_LIST) { $formula1 = str_replace(chr(0), ',', $formula1); } } catch (PhpSpreadsheetException $e) { return; } $offset += $sz1; // offset: var; size: 2; size of the formula data for the first condition $sz2 = self::getUInt2d($recordData, $offset); $offset += 2; // offset: var; size: 2; not used $offset += 2; // offset: var; size: $sz2; formula data for second condition (without size field) $formula2 = substr($recordData, $offset, $sz2); $formula2 = pack('v', $sz2) . $formula2; // prepend the length try { $formula2 = $this->getFormulaFromStructure($formula2); } catch (PhpSpreadsheetException $e) { return; } $offset += $sz2; // offset: var; size: var; cell range address list with $cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset)); $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses']; foreach ($cellRangeAddresses as $cellRange) { $stRange = $this->phpSheet->shrinkRangeToFit($cellRange); foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $coordinate) { $objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation(); $objValidation->setType($type); $objValidation->setErrorStyle($errorStyle); $objValidation->setAllowBlank((bool) $allowBlank); $objValidation->setShowInputMessage((bool) $showInputMessage); $objValidation->setShowErrorMessage((bool) $showErrorMessage); $objValidation->setShowDropDown(!$suppressDropDown); $objValidation->setOperator($operator); $objValidation->setErrorTitle($errorTitle); $objValidation->setError($error); $objValidation->setPromptTitle($promptTitle); $objValidation->setPrompt($prompt); $objValidation->setFormula1($formula1); $objValidation->setFormula2($formula2); } } } /** * Read SHEETLAYOUT record. Stores sheet tab color information. */ private function readSheetLayout(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // local pointer in record data $offset = 0; if (!$this->readDataOnly) { // offset: 0; size: 2; repeated record identifier 0x0862 // offset: 2; size: 10; not used // offset: 12; size: 4; size of record data // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?) $sz = self::getInt4d($recordData, 12); switch ($sz) { case 0x14: // offset: 16; size: 2; color index for sheet tab $colorIndex = self::getUInt2d($recordData, 16); $color = Xls\Color::map($colorIndex, $this->palette, $this->version); $this->phpSheet->getTabColor()->setRGB($color['rgb']); break; case 0x28: // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007 return; } } } /** * Read SHEETPROTECTION record (FEATHEADR). */ private function readSheetProtection(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; if ($this->readDataOnly) { return; } // offset: 0; size: 2; repeated record header // offset: 2; size: 2; FRT cell reference flag (=0 currently) // offset: 4; size: 8; Currently not used and set to 0 // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag) $isf = self::getUInt2d($recordData, 12); if ($isf != 2) { return; } // offset: 14; size: 1; =1 since this is a feat header // offset: 15; size: 4; size of rgbHdrSData // rgbHdrSData, assume "Enhanced Protection" // offset: 19; size: 2; option flags $options = self::getUInt2d($recordData, 19); // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects // Note - do not negate $bool $bool = (0x0001 & $options) >> 0; $this->phpSheet->getProtection()->setObjects((bool) $bool); // bit: 1; mask 0x0002; edit scenarios // Note - do not negate $bool $bool = (0x0002 & $options) >> 1; $this->phpSheet->getProtection()->setScenarios((bool) $bool); // bit: 2; mask 0x0004; format cells $bool = (0x0004 & $options) >> 2; $this->phpSheet->getProtection()->setFormatCells(!$bool); // bit: 3; mask 0x0008; format columns $bool = (0x0008 & $options) >> 3; $this->phpSheet->getProtection()->setFormatColumns(!$bool); // bit: 4; mask 0x0010; format rows $bool = (0x0010 & $options) >> 4; $this->phpSheet->getProtection()->setFormatRows(!$bool); // bit: 5; mask 0x0020; insert columns $bool = (0x0020 & $options) >> 5; $this->phpSheet->getProtection()->setInsertColumns(!$bool); // bit: 6; mask 0x0040; insert rows $bool = (0x0040 & $options) >> 6; $this->phpSheet->getProtection()->setInsertRows(!$bool); // bit: 7; mask 0x0080; insert hyperlinks $bool = (0x0080 & $options) >> 7; $this->phpSheet->getProtection()->setInsertHyperlinks(!$bool); // bit: 8; mask 0x0100; delete columns $bool = (0x0100 & $options) >> 8; $this->phpSheet->getProtection()->setDeleteColumns(!$bool); // bit: 9; mask 0x0200; delete rows $bool = (0x0200 & $options) >> 9; $this->phpSheet->getProtection()->setDeleteRows(!$bool); // bit: 10; mask 0x0400; select locked cells // Note that this is opposite of most of above. $bool = (0x0400 & $options) >> 10; $this->phpSheet->getProtection()->setSelectLockedCells((bool) $bool); // bit: 11; mask 0x0800; sort cell range $bool = (0x0800 & $options) >> 11; $this->phpSheet->getProtection()->setSort(!$bool); // bit: 12; mask 0x1000; auto filter $bool = (0x1000 & $options) >> 12; $this->phpSheet->getProtection()->setAutoFilter(!$bool); // bit: 13; mask 0x2000; pivot tables $bool = (0x2000 & $options) >> 13; $this->phpSheet->getProtection()->setPivotTables(!$bool); // bit: 14; mask 0x4000; select unlocked cells // Note that this is opposite of most of above. $bool = (0x4000 & $options) >> 14; $this->phpSheet->getProtection()->setSelectUnlockedCells((bool) $bool); // offset: 21; size: 2; not used } /** * Read RANGEPROTECTION record * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification, * where it is referred to as FEAT record. */ private function readRangeProtection(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer to next record $this->pos += 4 + $length; // local pointer in record data $offset = 0; if (!$this->readDataOnly) { $offset += 12; // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag $isf = self::getUInt2d($recordData, 12); if ($isf != 2) { // we only read FEAT records of type 2 return; } $offset += 2; $offset += 5; // offset: 19; size: 2; count of ref ranges this feature is on $cref = self::getUInt2d($recordData, 19); $offset += 2; $offset += 6; // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record) $cellRanges = []; for ($i = 0; $i < $cref; ++$i) { try { $cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8)); } catch (PhpSpreadsheetException $e) { return; } $cellRanges[] = $cellRange; $offset += 8; } // offset: var; size: var; variable length of feature specific data $rgbFeat = substr($recordData, $offset); $offset += 4; // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit) $wPassword = self::getInt4d($recordData, $offset); $offset += 4; // Apply range protection to sheet if ($cellRanges) { $this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true); } } } /** * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented. * In this case, we must treat the CONTINUE record as a MSODRAWING record. */ private function readContinue(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // check if we are reading drawing data // this is in case a free CONTINUE record occurs in other circumstances we are unaware of if ($this->drawingData == '') { // move stream pointer to next record $this->pos += 4 + $length; return; } // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data if ($length < 4) { // move stream pointer to next record $this->pos += 4 + $length; return; } // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record // look inside CONTINUE record to see if it looks like a part of an Escher stream // we know that Escher stream may be split at least at // 0xF003 MsofbtSpgrContainer // 0xF004 MsofbtSpContainer // 0xF00D MsofbtClientTextbox $validSplitPoints = [0xF003, 0xF004, 0xF00D]; // add identifiers if we find more $splitPoint = self::getUInt2d($recordData, 2); if (in_array($splitPoint, $validSplitPoints)) { // get spliced record data (and move pointer to next record) $splicedRecordData = $this->getSplicedRecordData(); $this->drawingData .= $splicedRecordData['recordData']; return; } // move stream pointer to next record $this->pos += 4 + $length; } /** * Reads a record from current position in data stream and continues reading data as long as CONTINUE * records are found. Splices the record data pieces and returns the combined string as if record data * is in one piece. * Moves to next current position in data stream to start of next record different from a CONtINUE record. * * @return array */ private function getSplicedRecordData() { $data = ''; $spliceOffsets = []; $i = 0; $spliceOffsets[0] = 0; do { ++$i; // offset: 0; size: 2; identifier $identifier = self::getUInt2d($this->data, $this->pos); // offset: 2; size: 2; length $length = self::getUInt2d($this->data, $this->pos + 2); $data .= $this->readRecordData($this->data, $this->pos + 4, $length); $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length; $this->pos += 4 + $length; $nextIdentifier = self::getUInt2d($this->data, $this->pos); } while ($nextIdentifier == self::XLS_TYPE_CONTINUE); return [ 'recordData' => $data, 'spliceOffsets' => $spliceOffsets, ]; } /** * Convert formula structure into human readable Excel formula like 'A3+A5*5'. * * @param string $formulaStructure The complete binary data for the formula * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas * * @return string Human readable formula */ private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1') { // offset: 0; size: 2; size of the following formula data $sz = self::getUInt2d($formulaStructure, 0); // offset: 2; size: sz $formulaData = substr($formulaStructure, 2, $sz); // offset: 2 + sz; size: variable (optional) if (strlen($formulaStructure) > 2 + $sz) { $additionalData = substr($formulaStructure, 2 + $sz); } else { $additionalData = ''; } return $this->getFormulaFromData($formulaData, $additionalData, $baseCell); } /** * Take formula data and additional data for formula and return human readable formula. * * @param string $formulaData The binary data for the formula itself * @param string $additionalData Additional binary data going with the formula * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas * * @return string Human readable formula */ private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1') { // start parsing the formula data $tokens = []; while (strlen($formulaData) > 0 && $token = $this->getNextToken($formulaData, $baseCell)) { $tokens[] = $token; $formulaData = substr($formulaData, $token['size']); } $formulaString = $this->createFormulaFromTokens($tokens, $additionalData); return $formulaString; } /** * Take array of tokens together with additional data for formula and return human readable formula. * * @param array $tokens * @param string $additionalData Additional binary data going with the formula * * @return string Human readable formula */ private function createFormulaFromTokens($tokens, $additionalData) { // empty formula? if (empty($tokens)) { return ''; } $formulaStrings = []; foreach ($tokens as $token) { // initialize spaces $space0 = $space0 ?? ''; // spaces before next token, not tParen $space1 = $space1 ?? ''; // carriage returns before next token, not tParen $space2 = $space2 ?? ''; // spaces before opening parenthesis $space3 = $space3 ?? ''; // carriage returns before opening parenthesis $space4 = $space4 ?? ''; // spaces before closing parenthesis $space5 = $space5 ?? ''; // carriage returns before closing parenthesis switch ($token['name']) { case 'tAdd': // addition case 'tConcat': // addition case 'tDiv': // division case 'tEQ': // equality case 'tGE': // greater than or equal case 'tGT': // greater than case 'tIsect': // intersection case 'tLE': // less than or equal case 'tList': // less than or equal case 'tLT': // less than case 'tMul': // multiplication case 'tNE': // multiplication case 'tPower': // power case 'tRange': // range case 'tSub': // subtraction $op2 = array_pop($formulaStrings); $op1 = array_pop($formulaStrings); $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2"; unset($space0, $space1); break; case 'tUplus': // unary plus case 'tUminus': // unary minus $op = array_pop($formulaStrings); $formulaStrings[] = "$space1$space0{$token['data']}$op"; unset($space0, $space1); break; case 'tPercent': // percent sign $op = array_pop($formulaStrings); $formulaStrings[] = "$op$space1$space0{$token['data']}"; unset($space0, $space1); break; case 'tAttrVolatile': // indicates volatile function case 'tAttrIf': case 'tAttrSkip': case 'tAttrChoose': // token is only important for Excel formula evaluator // do nothing break; case 'tAttrSpace': // space / carriage return // space will be used when next token arrives, do not alter formulaString stack switch ($token['data']['spacetype']) { case 'type0': $space0 = str_repeat(' ', $token['data']['spacecount']); break; case 'type1': $space1 = str_repeat("\n", $token['data']['spacecount']); break; case 'type2': $space2 = str_repeat(' ', $token['data']['spacecount']); break; case 'type3': $space3 = str_repeat("\n", $token['data']['spacecount']); break; case 'type4': $space4 = str_repeat(' ', $token['data']['spacecount']); break; case 'type5': $space5 = str_repeat("\n", $token['data']['spacecount']); break; } break; case 'tAttrSum': // SUM function with one parameter $op = array_pop($formulaStrings); $formulaStrings[] = "{$space1}{$space0}SUM($op)"; unset($space0, $space1); break; case 'tFunc': // function with fixed number of arguments case 'tFuncV': // function with variable number of arguments if ($token['data']['function'] != '') { // normal function $ops = []; // array of operators for ($i = 0; $i < $token['data']['args']; ++$i) { $ops[] = array_pop($formulaStrings); } $ops = array_reverse($ops); $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')'; unset($space0, $space1); } else { // add-in function $ops = []; // array of operators for ($i = 0; $i < $token['data']['args'] - 1; ++$i) { $ops[] = array_pop($formulaStrings); } $ops = array_reverse($ops); $function = array_pop($formulaStrings); $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ')'; unset($space0, $space1); } break; case 'tParen': // parenthesis $expression = array_pop($formulaStrings); $formulaStrings[] = "$space3$space2($expression$space5$space4)"; unset($space2, $space3, $space4, $space5); break; case 'tArray': // array constant $constantArray = self::readBIFF8ConstantArray($additionalData); $formulaStrings[] = $space1 . $space0 . $constantArray['value']; $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data unset($space0, $space1); break; case 'tMemArea': // bite off chunk of additional data $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData); $additionalData = substr($additionalData, $cellRangeAddressList['size']); $formulaStrings[] = "$space1$space0{$token['data']}"; unset($space0, $space1); break; case 'tArea': // cell range address case 'tBool': // boolean case 'tErr': // error code case 'tInt': // integer case 'tMemErr': case 'tMemFunc': case 'tMissArg': case 'tName': case 'tNameX': case 'tNum': // number case 'tRef': // single cell reference case 'tRef3d': // 3d cell reference case 'tArea3d': // 3d cell range reference case 'tRefN': case 'tAreaN': case 'tStr': // string $formulaStrings[] = "$space1$space0{$token['data']}"; unset($space0, $space1); break; } } $formulaString = $formulaStrings[0]; return $formulaString; } /** * Fetch next token from binary formula data. * * @param string $formulaData Formula data * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas * * @return array */ private function getNextToken($formulaData, $baseCell = 'A1') { // offset: 0; size: 1; token id $id = ord($formulaData[0]); // token id $name = false; // initialize token name switch ($id) { case 0x03: $name = 'tAdd'; $size = 1; $data = '+'; break; case 0x04: $name = 'tSub'; $size = 1; $data = '-'; break; case 0x05: $name = 'tMul'; $size = 1; $data = '*'; break; case 0x06: $name = 'tDiv'; $size = 1; $data = '/'; break; case 0x07: $name = 'tPower'; $size = 1; $data = '^'; break; case 0x08: $name = 'tConcat'; $size = 1; $data = '&'; break; case 0x09: $name = 'tLT'; $size = 1; $data = '<'; break; case 0x0A: $name = 'tLE'; $size = 1; $data = '<='; break; case 0x0B: $name = 'tEQ'; $size = 1; $data = '='; break; case 0x0C: $name = 'tGE'; $size = 1; $data = '>='; break; case 0x0D: $name = 'tGT'; $size = 1; $data = '>'; break; case 0x0E: $name = 'tNE'; $size = 1; $data = '<>'; break; case 0x0F: $name = 'tIsect'; $size = 1; $data = ' '; break; case 0x10: $name = 'tList'; $size = 1; $data = ','; break; case 0x11: $name = 'tRange'; $size = 1; $data = ':'; break; case 0x12: $name = 'tUplus'; $size = 1; $data = '+'; break; case 0x13: $name = 'tUminus'; $size = 1; $data = '-'; break; case 0x14: $name = 'tPercent'; $size = 1; $data = '%'; break; case 0x15: // parenthesis $name = 'tParen'; $size = 1; $data = null; break; case 0x16: // missing argument $name = 'tMissArg'; $size = 1; $data = ''; break; case 0x17: // string $name = 'tStr'; // offset: 1; size: var; Unicode string, 8-bit string length $string = self::readUnicodeStringShort(substr($formulaData, 1)); $size = 1 + $string['size']; $data = self::UTF8toExcelDoubleQuoted($string['value']); break; case 0x19: // Special attribute // offset: 1; size: 1; attribute type flags: switch (ord($formulaData[1])) { case 0x01: $name = 'tAttrVolatile'; $size = 4; $data = null; break; case 0x02: $name = 'tAttrIf'; $size = 4; $data = null; break; case 0x04: $name = 'tAttrChoose'; // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1) $nc = self::getUInt2d($formulaData, 2); // offset: 4; size: 2 * $nc // offset: 4 + 2 * $nc; size: 2 $size = 2 * $nc + 6; $data = null; break; case 0x08: $name = 'tAttrSkip'; $size = 4; $data = null; break; case 0x10: $name = 'tAttrSum'; $size = 4; $data = null; break; case 0x40: case 0x41: $name = 'tAttrSpace'; $size = 4; // offset: 2; size: 2; space type and position switch (ord($formulaData[2])) { case 0x00: $spacetype = 'type0'; break; case 0x01: $spacetype = 'type1'; break; case 0x02: $spacetype = 'type2'; break; case 0x03: $spacetype = 'type3'; break; case 0x04: $spacetype = 'type4'; break; case 0x05: $spacetype = 'type5'; break; default: throw new Exception('Unrecognized space type in tAttrSpace token'); } // offset: 3; size: 1; number of inserted spaces/carriage returns $spacecount = ord($formulaData[3]); $data = ['spacetype' => $spacetype, 'spacecount' => $spacecount]; break; default: throw new Exception('Unrecognized attribute flag in tAttr token'); } break; case 0x1C: // error code // offset: 1; size: 1; error code $name = 'tErr'; $size = 2; $data = Xls\ErrorCode::lookup(ord($formulaData[1])); break; case 0x1D: // boolean // offset: 1; size: 1; 0 = false, 1 = true; $name = 'tBool'; $size = 2; $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE'; break; case 0x1E: // integer // offset: 1; size: 2; unsigned 16-bit integer $name = 'tInt'; $size = 3; $data = self::getUInt2d($formulaData, 1); break; case 0x1F: // number // offset: 1; size: 8; $name = 'tNum'; $size = 9; $data = self::extractNumber(substr($formulaData, 1)); $data = str_replace(',', '.', (string) $data); // in case non-English locale break; case 0x20: // array constant case 0x40: case 0x60: // offset: 1; size: 7; not used $name = 'tArray'; $size = 8; $data = null; break; case 0x21: // function with fixed number of arguments case 0x41: case 0x61: $name = 'tFunc'; $size = 3; // offset: 1; size: 2; index to built-in sheet function switch (self::getUInt2d($formulaData, 1)) { case 2: $function = 'ISNA'; $args = 1; break; case 3: $function = 'ISERROR'; $args = 1; break; case 10: $function = 'NA'; $args = 0; break; case 15: $function = 'SIN'; $args = 1; break; case 16: $function = 'COS'; $args = 1; break; case 17: $function = 'TAN'; $args = 1; break; case 18: $function = 'ATAN'; $args = 1; break; case 19: $function = 'PI'; $args = 0; break; case 20: $function = 'SQRT'; $args = 1; break; case 21: $function = 'EXP'; $args = 1; break; case 22: $function = 'LN'; $args = 1; break; case 23: $function = 'LOG10'; $args = 1; break; case 24: $function = 'ABS'; $args = 1; break; case 25: $function = 'INT'; $args = 1; break; case 26: $function = 'SIGN'; $args = 1; break; case 27: $function = 'ROUND'; $args = 2; break; case 30: $function = 'REPT'; $args = 2; break; case 31: $function = 'MID'; $args = 3; break; case 32: $function = 'LEN'; $args = 1; break; case 33: $function = 'VALUE'; $args = 1; break; case 34: $function = 'TRUE'; $args = 0; break; case 35: $function = 'FALSE'; $args = 0; break; case 38: $function = 'NOT'; $args = 1; break; case 39: $function = 'MOD'; $args = 2; break; case 40: $function = 'DCOUNT'; $args = 3; break; case 41: $function = 'DSUM'; $args = 3; break; case 42: $function = 'DAVERAGE'; $args = 3; break; case 43: $function = 'DMIN'; $args = 3; break; case 44: $function = 'DMAX'; $args = 3; break; case 45: $function = 'DSTDEV'; $args = 3; break; case 48: $function = 'TEXT'; $args = 2; break; case 61: $function = 'MIRR'; $args = 3; break; case 63: $function = 'RAND'; $args = 0; break; case 65: $function = 'DATE'; $args = 3; break; case 66: $function = 'TIME'; $args = 3; break; case 67: $function = 'DAY'; $args = 1; break; case 68: $function = 'MONTH'; $args = 1; break; case 69: $function = 'YEAR'; $args = 1; break; case 71: $function = 'HOUR'; $args = 1; break; case 72: $function = 'MINUTE'; $args = 1; break; case 73: $function = 'SECOND'; $args = 1; break; case 74: $function = 'NOW'; $args = 0; break; case 75: $function = 'AREAS'; $args = 1; break; case 76: $function = 'ROWS'; $args = 1; break; case 77: $function = 'COLUMNS'; $args = 1; break; case 83: $function = 'TRANSPOSE'; $args = 1; break; case 86: $function = 'TYPE'; $args = 1; break; case 97: $function = 'ATAN2'; $args = 2; break; case 98: $function = 'ASIN'; $args = 1; break; case 99: $function = 'ACOS'; $args = 1; break; case 105: $function = 'ISREF'; $args = 1; break; case 111: $function = 'CHAR'; $args = 1; break; case 112: $function = 'LOWER'; $args = 1; break; case 113: $function = 'UPPER'; $args = 1; break; case 114: $function = 'PROPER'; $args = 1; break; case 117: $function = 'EXACT'; $args = 2; break; case 118: $function = 'TRIM'; $args = 1; break; case 119: $function = 'REPLACE'; $args = 4; break; case 121: $function = 'CODE'; $args = 1; break; case 126: $function = 'ISERR'; $args = 1; break; case 127: $function = 'ISTEXT'; $args = 1; break; case 128: $function = 'ISNUMBER'; $args = 1; break; case 129: $function = 'ISBLANK'; $args = 1; break; case 130: $function = 'T'; $args = 1; break; case 131: $function = 'N'; $args = 1; break; case 140: $function = 'DATEVALUE'; $args = 1; break; case 141: $function = 'TIMEVALUE'; $args = 1; break; case 142: $function = 'SLN'; $args = 3; break; case 143: $function = 'SYD'; $args = 4; break; case 162: $function = 'CLEAN'; $args = 1; break; case 163: $function = 'MDETERM'; $args = 1; break; case 164: $function = 'MINVERSE'; $args = 1; break; case 165: $function = 'MMULT'; $args = 2; break; case 184: $function = 'FACT'; $args = 1; break; case 189: $function = 'DPRODUCT'; $args = 3; break; case 190: $function = 'ISNONTEXT'; $args = 1; break; case 195: $function = 'DSTDEVP'; $args = 3; break; case 196: $function = 'DVARP'; $args = 3; break; case 198: $function = 'ISLOGICAL'; $args = 1; break; case 199: $function = 'DCOUNTA'; $args = 3; break; case 207: $function = 'REPLACEB'; $args = 4; break; case 210: $function = 'MIDB'; $args = 3; break; case 211: $function = 'LENB'; $args = 1; break; case 212: $function = 'ROUNDUP'; $args = 2; break; case 213: $function = 'ROUNDDOWN'; $args = 2; break; case 214: $function = 'ASC'; $args = 1; break; case 215: $function = 'DBCS'; $args = 1; break; case 221: $function = 'TODAY'; $args = 0; break; case 229: $function = 'SINH'; $args = 1; break; case 230: $function = 'COSH'; $args = 1; break; case 231: $function = 'TANH'; $args = 1; break; case 232: $function = 'ASINH'; $args = 1; break; case 233: $function = 'ACOSH'; $args = 1; break; case 234: $function = 'ATANH'; $args = 1; break; case 235: $function = 'DGET'; $args = 3; break; case 244: $function = 'INFO'; $args = 1; break; case 252: $function = 'FREQUENCY'; $args = 2; break; case 261: $function = 'ERROR.TYPE'; $args = 1; break; case 271: $function = 'GAMMALN'; $args = 1; break; case 273: $function = 'BINOMDIST'; $args = 4; break; case 274: $function = 'CHIDIST'; $args = 2; break; case 275: $function = 'CHIINV'; $args = 2; break; case 276: $function = 'COMBIN'; $args = 2; break; case 277: $function = 'CONFIDENCE'; $args = 3; break; case 278: $function = 'CRITBINOM'; $args = 3; break; case 279: $function = 'EVEN'; $args = 1; break; case 280: $function = 'EXPONDIST'; $args = 3; break; case 281: $function = 'FDIST'; $args = 3; break; case 282: $function = 'FINV'; $args = 3; break; case 283: $function = 'FISHER'; $args = 1; break; case 284: $function = 'FISHERINV'; $args = 1; break; case 285: $function = 'FLOOR'; $args = 2; break; case 286: $function = 'GAMMADIST'; $args = 4; break; case 287: $function = 'GAMMAINV'; $args = 3; break; case 288: $function = 'CEILING'; $args = 2; break; case 289: $function = 'HYPGEOMDIST'; $args = 4; break; case 290: $function = 'LOGNORMDIST'; $args = 3; break; case 291: $function = 'LOGINV'; $args = 3; break; case 292: $function = 'NEGBINOMDIST'; $args = 3; break; case 293: $function = 'NORMDIST'; $args = 4; break; case 294: $function = 'NORMSDIST'; $args = 1; break; case 295: $function = 'NORMINV'; $args = 3; break; case 296: $function = 'NORMSINV'; $args = 1; break; case 297: $function = 'STANDARDIZE'; $args = 3; break; case 298: $function = 'ODD'; $args = 1; break; case 299: $function = 'PERMUT'; $args = 2; break; case 300: $function = 'POISSON'; $args = 3; break; case 301: $function = 'TDIST'; $args = 3; break; case 302: $function = 'WEIBULL'; $args = 4; break; case 303: $function = 'SUMXMY2'; $args = 2; break; case 304: $function = 'SUMX2MY2'; $args = 2; break; case 305: $function = 'SUMX2PY2'; $args = 2; break; case 306: $function = 'CHITEST'; $args = 2; break; case 307: $function = 'CORREL'; $args = 2; break; case 308: $function = 'COVAR'; $args = 2; break; case 309: $function = 'FORECAST'; $args = 3; break; case 310: $function = 'FTEST'; $args = 2; break; case 311: $function = 'INTERCEPT'; $args = 2; break; case 312: $function = 'PEARSON'; $args = 2; break; case 313: $function = 'RSQ'; $args = 2; break; case 314: $function = 'STEYX'; $args = 2; break; case 315: $function = 'SLOPE'; $args = 2; break; case 316: $function = 'TTEST'; $args = 4; break; case 325: $function = 'LARGE'; $args = 2; break; case 326: $function = 'SMALL'; $args = 2; break; case 327: $function = 'QUARTILE'; $args = 2; break; case 328: $function = 'PERCENTILE'; $args = 2; break; case 331: $function = 'TRIMMEAN'; $args = 2; break; case 332: $function = 'TINV'; $args = 2; break; case 337: $function = 'POWER'; $args = 2; break; case 342: $function = 'RADIANS'; $args = 1; break; case 343: $function = 'DEGREES'; $args = 1; break; case 346: $function = 'COUNTIF'; $args = 2; break; case 347: $function = 'COUNTBLANK'; $args = 1; break; case 350: $function = 'ISPMT'; $args = 4; break; case 351: $function = 'DATEDIF'; $args = 3; break; case 352: $function = 'DATESTRING'; $args = 1; break; case 353: $function = 'NUMBERSTRING'; $args = 2; break; case 360: $function = 'PHONETIC'; $args = 1; break; case 368: $function = 'BAHTTEXT'; $args = 1; break; default: throw new Exception('Unrecognized function in formula'); } $data = ['function' => $function, 'args' => $args]; break; case 0x22: // function with variable number of arguments case 0x42: case 0x62: $name = 'tFuncV'; $size = 4; // offset: 1; size: 1; number of arguments $args = ord($formulaData[1]); // offset: 2: size: 2; index to built-in sheet function $index = self::getUInt2d($formulaData, 2); switch ($index) { case 0: $function = 'COUNT'; break; case 1: $function = 'IF'; break; case 4: $function = 'SUM'; break; case 5: $function = 'AVERAGE'; break; case 6: $function = 'MIN'; break; case 7: $function = 'MAX'; break; case 8: $function = 'ROW'; break; case 9: $function = 'COLUMN'; break; case 11: $function = 'NPV'; break; case 12: $function = 'STDEV'; break; case 13: $function = 'DOLLAR'; break; case 14: $function = 'FIXED'; break; case 28: $function = 'LOOKUP'; break; case 29: $function = 'INDEX'; break; case 36: $function = 'AND'; break; case 37: $function = 'OR'; break; case 46: $function = 'VAR'; break; case 49: $function = 'LINEST'; break; case 50: $function = 'TREND'; break; case 51: $function = 'LOGEST'; break; case 52: $function = 'GROWTH'; break; case 56: $function = 'PV'; break; case 57: $function = 'FV'; break; case 58: $function = 'NPER'; break; case 59: $function = 'PMT'; break; case 60: $function = 'RATE'; break; case 62: $function = 'IRR'; break; case 64: $function = 'MATCH'; break; case 70: $function = 'WEEKDAY'; break; case 78: $function = 'OFFSET'; break; case 82: $function = 'SEARCH'; break; case 100: $function = 'CHOOSE'; break; case 101: $function = 'HLOOKUP'; break; case 102: $function = 'VLOOKUP'; break; case 109: $function = 'LOG'; break; case 115: $function = 'LEFT'; break; case 116: $function = 'RIGHT'; break; case 120: $function = 'SUBSTITUTE'; break; case 124: $function = 'FIND'; break; case 125: $function = 'CELL'; break; case 144: $function = 'DDB'; break; case 148: $function = 'INDIRECT'; break; case 167: $function = 'IPMT'; break; case 168: $function = 'PPMT'; break; case 169: $function = 'COUNTA'; break; case 183: $function = 'PRODUCT'; break; case 193: $function = 'STDEVP'; break; case 194: $function = 'VARP'; break; case 197: $function = 'TRUNC'; break; case 204: $function = 'USDOLLAR'; break; case 205: $function = 'FINDB'; break; case 206: $function = 'SEARCHB'; break; case 208: $function = 'LEFTB'; break; case 209: $function = 'RIGHTB'; break; case 216: $function = 'RANK'; break; case 219: $function = 'ADDRESS'; break; case 220: $function = 'DAYS360'; break; case 222: $function = 'VDB'; break; case 227: $function = 'MEDIAN'; break; case 228: $function = 'SUMPRODUCT'; break; case 247: $function = 'DB'; break; case 255: $function = ''; break; case 269: $function = 'AVEDEV'; break; case 270: $function = 'BETADIST'; break; case 272: $function = 'BETAINV'; break; case 317: $function = 'PROB'; break; case 318: $function = 'DEVSQ'; break; case 319: $function = 'GEOMEAN'; break; case 320: $function = 'HARMEAN'; break; case 321: $function = 'SUMSQ'; break; case 322: $function = 'KURT'; break; case 323: $function = 'SKEW'; break; case 324: $function = 'ZTEST'; break; case 329: $function = 'PERCENTRANK'; break; case 330: $function = 'MODE'; break; case 336: $function = 'CONCATENATE'; break; case 344: $function = 'SUBTOTAL'; break; case 345: $function = 'SUMIF'; break; case 354: $function = 'ROMAN'; break; case 358: $function = 'GETPIVOTDATA'; break; case 359: $function = 'HYPERLINK'; break; case 361: $function = 'AVERAGEA'; break; case 362: $function = 'MAXA'; break; case 363: $function = 'MINA'; break; case 364: $function = 'STDEVPA'; break; case 365: $function = 'VARPA'; break; case 366: $function = 'STDEVA'; break; case 367: $function = 'VARA'; break; default: throw new Exception('Unrecognized function in formula'); } $data = ['function' => $function, 'args' => $args]; break; case 0x23: // index to defined name case 0x43: case 0x63: $name = 'tName'; $size = 5; // offset: 1; size: 2; one-based index to definedname record $definedNameIndex = self::getUInt2d($formulaData, 1) - 1; // offset: 2; size: 2; not used $data = $this->definedname[$definedNameIndex]['name'] ?? ''; break; case 0x24: // single cell reference e.g. A5 case 0x44: case 0x64: $name = 'tRef'; $size = 5; $data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4)); break; case 0x25: // cell range reference to cells in the same sheet (2d) case 0x45: case 0x65: $name = 'tArea'; $size = 9; $data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8)); break; case 0x26: // Constant reference sub-expression case 0x46: case 0x66: $name = 'tMemArea'; // offset: 1; size: 4; not used // offset: 5; size: 2; size of the following subexpression $subSize = self::getUInt2d($formulaData, 5); $size = 7 + $subSize; $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize)); break; case 0x27: // Deleted constant reference sub-expression case 0x47: case 0x67: $name = 'tMemErr'; // offset: 1; size: 4; not used // offset: 5; size: 2; size of the following subexpression $subSize = self::getUInt2d($formulaData, 5); $size = 7 + $subSize; $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize)); break; case 0x29: // Variable reference sub-expression case 0x49: case 0x69: $name = 'tMemFunc'; // offset: 1; size: 2; size of the following sub-expression $subSize = self::getUInt2d($formulaData, 1); $size = 3 + $subSize; $data = $this->getFormulaFromData(substr($formulaData, 3, $subSize)); break; case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places case 0x4C: case 0x6C: $name = 'tRefN'; $size = 5; $data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell); break; case 0x2D: // Relative 2d range reference case 0x4D: case 0x6D: $name = 'tAreaN'; $size = 9; $data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell); break; case 0x39: // External name case 0x59: case 0x79: $name = 'tNameX'; $size = 7; // offset: 1; size: 2; index to REF entry in EXTERNSHEET record // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record $index = self::getUInt2d($formulaData, 3); // assume index is to EXTERNNAME record $data = $this->externalNames[$index - 1]['name'] ?? ''; // offset: 5; size: 2; not used break; case 0x3A: // 3d reference to cell case 0x5A: case 0x7A: $name = 'tRef3d'; $size = 7; try { // offset: 1; size: 2; index to REF entry $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1)); // offset: 3; size: 4; cell address $cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4)); $data = "$sheetRange!$cellAddress"; } catch (PhpSpreadsheetException $e) { // deleted sheet reference $data = '#REF!'; } break; case 0x3B: // 3d reference to cell range case 0x5B: case 0x7B: $name = 'tArea3d'; $size = 11; try { // offset: 1; size: 2; index to REF entry $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1)); // offset: 3; size: 8; cell address $cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8)); $data = "$sheetRange!$cellRangeAddress"; } catch (PhpSpreadsheetException $e) { // deleted sheet reference $data = '#REF!'; } break; // Unknown cases // don't know how to deal with default: throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula'); } return [ 'id' => $id, 'name' => $name, 'size' => $size, 'data' => $data, ]; } /** * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2' * section 3.3.4. * * @param string $cellAddressStructure * * @return string */ private function readBIFF8CellAddress($cellAddressStructure) { // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767)) $row = self::getUInt2d($cellAddressStructure, 0) + 1; // offset: 2; size: 2; index to column or column offset + relative flags // bit: 7-0; mask 0x00FF; column index $column = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($cellAddressStructure, 2)) + 1); // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) { $column = '$' . $column; } // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) { $row = '$' . $row; } return $column . $row; } /** * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column * to indicate offsets from a base cell * section 3.3.4. * * @param string $cellAddressStructure * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas * * @return string */ private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1') { [$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell); $baseCol = Coordinate::columnIndexFromString($baseCol) - 1; $baseRow = (int) $baseRow; // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767)) $rowIndex = self::getUInt2d($cellAddressStructure, 0); $row = self::getUInt2d($cellAddressStructure, 0) + 1; // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) { // offset: 2; size: 2; index to column or column offset + relative flags // bit: 7-0; mask 0x00FF; column index $colIndex = 0x00FF & self::getUInt2d($cellAddressStructure, 2); $column = Coordinate::stringFromColumnIndex($colIndex + 1); $column = '$' . $column; } else { // offset: 2; size: 2; index to column or column offset + relative flags // bit: 7-0; mask 0x00FF; column index $relativeColIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2); $colIndex = $baseCol + $relativeColIndex; $colIndex = ($colIndex < 256) ? $colIndex : $colIndex - 256; $colIndex = ($colIndex >= 0) ? $colIndex : $colIndex + 256; $column = Coordinate::stringFromColumnIndex($colIndex + 1); } // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) { $row = '$' . $row; } else { $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536; $row = $baseRow + $rowIndex; } return $column . $row; } /** * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1' * always fixed range * section 2.5.14. * * @param string $subData * * @return string */ private function readBIFF5CellRangeAddressFixed($subData) { // offset: 0; size: 2; index to first row $fr = self::getUInt2d($subData, 0) + 1; // offset: 2; size: 2; index to last row $lr = self::getUInt2d($subData, 2) + 1; // offset: 4; size: 1; index to first column $fc = ord($subData[4]); // offset: 5; size: 1; index to last column $lc = ord($subData[5]); // check values if ($fr > $lr || $fc > $lc) { throw new Exception('Not a cell range address'); } // column index to letter $fc = Coordinate::stringFromColumnIndex($fc + 1); $lc = Coordinate::stringFromColumnIndex($lc + 1); if ($fr == $lr && $fc == $lc) { return "$fc$fr"; } return "$fc$fr:$lc$lr"; } /** * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1' * always fixed range * section 2.5.14. * * @param string $subData * * @return string */ private function readBIFF8CellRangeAddressFixed($subData) { // offset: 0; size: 2; index to first row $fr = self::getUInt2d($subData, 0) + 1; // offset: 2; size: 2; index to last row $lr = self::getUInt2d($subData, 2) + 1; // offset: 4; size: 2; index to first column $fc = self::getUInt2d($subData, 4); // offset: 6; size: 2; index to last column $lc = self::getUInt2d($subData, 6); // check values if ($fr > $lr || $fc > $lc) { throw new Exception('Not a cell range address'); } // column index to letter $fc = Coordinate::stringFromColumnIndex($fc + 1); $lc = Coordinate::stringFromColumnIndex($lc + 1); if ($fr == $lr && $fc == $lc) { return "$fc$fr"; } return "$fc$fr:$lc$lr"; } /** * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6' * there are flags indicating whether column/row index is relative * section 3.3.4. * * @param string $subData * * @return string */ private function readBIFF8CellRangeAddress($subData) { // todo: if cell range is just a single cell, should this funciton // not just return e.g. 'A1' and not 'A1:A1' ? // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767)) $fr = self::getUInt2d($subData, 0) + 1; // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767)) $lr = self::getUInt2d($subData, 2) + 1; // offset: 4; size: 2; index to first column or column offset + relative flags // bit: 7-0; mask 0x00FF; column index $fc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 4)) + 1); // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) if (!(0x4000 & self::getUInt2d($subData, 4))) { $fc = '$' . $fc; } // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) if (!(0x8000 & self::getUInt2d($subData, 4))) { $fr = '$' . $fr; } // offset: 6; size: 2; index to last column or column offset + relative flags // bit: 7-0; mask 0x00FF; column index $lc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 6)) + 1); // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) if (!(0x4000 & self::getUInt2d($subData, 6))) { $lc = '$' . $lc; } // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) if (!(0x8000 & self::getUInt2d($subData, 6))) { $lr = '$' . $lr; } return "$fc$fr:$lc$lr"; } /** * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column * to indicate offsets from a base cell * section 3.3.4. * * @param string $subData * @param string $baseCell Base cell * * @return string Cell range address */ private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1') { [$baseCol, $baseRow] = Coordinate::indexesFromString($baseCell); $baseCol = $baseCol - 1; // TODO: if cell range is just a single cell, should this funciton // not just return e.g. 'A1' and not 'A1:A1' ? // offset: 0; size: 2; first row $frIndex = self::getUInt2d($subData, 0); // adjust below // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767) $lrIndex = self::getUInt2d($subData, 2); // adjust below // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) if (!(0x4000 & self::getUInt2d($subData, 4))) { // absolute column index // offset: 4; size: 2; first column with relative/absolute flags // bit: 7-0; mask 0x00FF; column index $fcIndex = 0x00FF & self::getUInt2d($subData, 4); $fc = Coordinate::stringFromColumnIndex($fcIndex + 1); $fc = '$' . $fc; } else { // column offset // offset: 4; size: 2; first column with relative/absolute flags // bit: 7-0; mask 0x00FF; column index $relativeFcIndex = 0x00FF & self::getInt2d($subData, 4); $fcIndex = $baseCol + $relativeFcIndex; $fcIndex = ($fcIndex < 256) ? $fcIndex : $fcIndex - 256; $fcIndex = ($fcIndex >= 0) ? $fcIndex : $fcIndex + 256; $fc = Coordinate::stringFromColumnIndex($fcIndex + 1); } // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) if (!(0x8000 & self::getUInt2d($subData, 4))) { // absolute row index $fr = $frIndex + 1; $fr = '$' . $fr; } else { // row offset $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536; $fr = $baseRow + $frIndex; } // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) if (!(0x4000 & self::getUInt2d($subData, 6))) { // absolute column index // offset: 6; size: 2; last column with relative/absolute flags // bit: 7-0; mask 0x00FF; column index $lcIndex = 0x00FF & self::getUInt2d($subData, 6); $lc = Coordinate::stringFromColumnIndex($lcIndex + 1); $lc = '$' . $lc; } else { // column offset // offset: 4; size: 2; first column with relative/absolute flags // bit: 7-0; mask 0x00FF; column index $relativeLcIndex = 0x00FF & self::getInt2d($subData, 4); $lcIndex = $baseCol + $relativeLcIndex; $lcIndex = ($lcIndex < 256) ? $lcIndex : $lcIndex - 256; $lcIndex = ($lcIndex >= 0) ? $lcIndex : $lcIndex + 256; $lc = Coordinate::stringFromColumnIndex($lcIndex + 1); } // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) if (!(0x8000 & self::getUInt2d($subData, 6))) { // absolute row index $lr = $lrIndex + 1; $lr = '$' . $lr; } else { // row offset $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536; $lr = $baseRow + $lrIndex; } return "$fc$fr:$lc$lr"; } /** * Read BIFF8 cell range address list * section 2.5.15. * * @param string $subData * * @return array */ private function readBIFF8CellRangeAddressList($subData) { $cellRangeAddresses = []; // offset: 0; size: 2; number of the following cell range addresses $nm = self::getUInt2d($subData, 0); $offset = 2; // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses for ($i = 0; $i < $nm; ++$i) { $cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8)); $offset += 8; } return [ 'size' => 2 + 8 * $nm, 'cellRangeAddresses' => $cellRangeAddresses, ]; } /** * Read BIFF5 cell range address list * section 2.5.15. * * @param string $subData * * @return array */ private function readBIFF5CellRangeAddressList($subData) { $cellRangeAddresses = []; // offset: 0; size: 2; number of the following cell range addresses $nm = self::getUInt2d($subData, 0); $offset = 2; // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses for ($i = 0; $i < $nm; ++$i) { $cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6)); $offset += 6; } return [ 'size' => 2 + 6 * $nm, 'cellRangeAddresses' => $cellRangeAddresses, ]; } /** * Get a sheet range like Sheet1:Sheet3 from REF index * Note: If there is only one sheet in the range, one gets e.g Sheet1 * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets, * in which case an Exception is thrown. * * @param int $index * * @return false|string */ private function readSheetRangeByRefIndex($index) { if (isset($this->ref[$index])) { $type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type']; switch ($type) { case 'internal': // check if we have a deleted 3d reference if ($this->ref[$index]['firstSheetIndex'] == 0xFFFF || $this->ref[$index]['lastSheetIndex'] == 0xFFFF) { throw new Exception('Deleted sheet reference'); } // we have normal sheet range (collapsed or uncollapsed) $firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name']; $lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name']; if ($firstSheetName == $lastSheetName) { // collapsed sheet range $sheetRange = $firstSheetName; } else { $sheetRange = "$firstSheetName:$lastSheetName"; } // escape the single-quotes $sheetRange = str_replace("'", "''", $sheetRange); // if there are special characters, we need to enclose the range in single-quotes // todo: check if we have identified the whole set of special characters // it seems that the following characters are not accepted for sheet names // and we may assume that they are not present: []*/:\? if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/u", $sheetRange)) { $sheetRange = "'$sheetRange'"; } return $sheetRange; default: // TODO: external sheet support throw new Exception('Xls reader only supports internal sheets in formulas'); } } return false; } /** * read BIFF8 constant value array from array data * returns e.g. ['value' => '{1,2;3,4}', 'size' => 40] * section 2.5.8. * * @param string $arrayData * * @return array */ private static function readBIFF8ConstantArray($arrayData) { // offset: 0; size: 1; number of columns decreased by 1 $nc = ord($arrayData[0]); // offset: 1; size: 2; number of rows decreased by 1 $nr = self::getUInt2d($arrayData, 1); $size = 3; // initialize $arrayData = substr($arrayData, 3); // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values $matrixChunks = []; for ($r = 1; $r <= $nr + 1; ++$r) { $items = []; for ($c = 1; $c <= $nc + 1; ++$c) { $constant = self::readBIFF8Constant($arrayData); $items[] = $constant['value']; $arrayData = substr($arrayData, $constant['size']); $size += $constant['size']; } $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"' } $matrix = '{' . implode(';', $matrixChunks) . '}'; return [ 'value' => $matrix, 'size' => $size, ]; } /** * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value' * section 2.5.7 * returns e.g. ['value' => '5', 'size' => 9]. * * @param string $valueData * * @return array */ private static function readBIFF8Constant($valueData) { // offset: 0; size: 1; identifier for type of constant $identifier = ord($valueData[0]); switch ($identifier) { case 0x00: // empty constant (what is this?) $value = ''; $size = 9; break; case 0x01: // number // offset: 1; size: 8; IEEE 754 floating-point value $value = self::extractNumber(substr($valueData, 1, 8)); $size = 9; break; case 0x02: // string value // offset: 1; size: var; Unicode string, 16-bit string length $string = self::readUnicodeStringLong(substr($valueData, 1)); $value = '"' . $string['value'] . '"'; $size = 1 + $string['size']; break; case 0x04: // boolean // offset: 1; size: 1; 0 = FALSE, 1 = TRUE if (ord($valueData[1])) { $value = 'TRUE'; } else { $value = 'FALSE'; } $size = 9; break; case 0x10: // error code // offset: 1; size: 1; error code $value = Xls\ErrorCode::lookup(ord($valueData[1])); $size = 9; break; default: throw new PhpSpreadsheetException('Unsupported BIFF8 constant'); } return [ 'value' => $value, 'size' => $size, ]; } /** * Extract RGB color * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4. * * @param string $rgb Encoded RGB value (4 bytes) * * @return array */ private static function readRGB($rgb) { // offset: 0; size 1; Red component $r = ord($rgb[0]); // offset: 1; size: 1; Green component $g = ord($rgb[1]); // offset: 2; size: 1; Blue component $b = ord($rgb[2]); // HEX notation, e.g. 'FF00FC' $rgb = sprintf('%02X%02X%02X', $r, $g, $b); return ['rgb' => $rgb]; } /** * Read byte string (8-bit string length) * OpenOffice documentation: 2.5.2. * * @param string $subData * * @return array */ private function readByteStringShort($subData) { // offset: 0; size: 1; length of the string (character count) $ln = ord($subData[0]); // offset: 1: size: var; character array (8-bit characters) $value = $this->decodeCodepage(substr($subData, 1, $ln)); return [ 'value' => $value, 'size' => 1 + $ln, // size in bytes of data structure ]; } /** * Read byte string (16-bit string length) * OpenOffice documentation: 2.5.2. * * @param string $subData * * @return array */ private function readByteStringLong($subData) { // offset: 0; size: 2; length of the string (character count) $ln = self::getUInt2d($subData, 0); // offset: 2: size: var; character array (8-bit characters) $value = $this->decodeCodepage(substr($subData, 2)); //return $string; return [ 'value' => $value, 'size' => 2 + $ln, // size in bytes of data structure ]; } /** * Extracts an Excel Unicode short string (8-bit string length) * OpenOffice documentation: 2.5.3 * function will automatically find out where the Unicode string ends. * * @param string $subData * * @return array */ private static function readUnicodeStringShort($subData) { $value = ''; // offset: 0: size: 1; length of the string (character count) $characterCount = ord($subData[0]); $string = self::readUnicodeString(substr($subData, 1), $characterCount); // add 1 for the string length ++$string['size']; return $string; } /** * Extracts an Excel Unicode long string (16-bit string length) * OpenOffice documentation: 2.5.3 * this function is under construction, needs to support rich text, and Asian phonetic settings. * * @param string $subData * * @return array */ private static function readUnicodeStringLong($subData) { $value = ''; // offset: 0: size: 2; length of the string (character count) $characterCount = self::getUInt2d($subData, 0); $string = self::readUnicodeString(substr($subData, 2), $characterCount); // add 2 for the string length $string['size'] += 2; return $string; } /** * Read Unicode string with no string length field, but with known character count * this function is under construction, needs to support rich text, and Asian phonetic settings * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3. * * @param string $subData * @param int $characterCount * * @return array */ private static function readUnicodeString($subData, $characterCount) { $value = ''; // offset: 0: size: 1; option flags // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit) $isCompressed = !((0x01 & ord($subData[0])) >> 0); // bit: 2; mask: 0x04; Asian phonetic settings $hasAsian = (0x04) & ord($subData[0]) >> 2; // bit: 3; mask: 0x08; Rich-Text settings $hasRichText = (0x08) & ord($subData[0]) >> 3; // offset: 1: size: var; character array // this offset assumes richtext and Asian phonetic settings are off which is generally wrong // needs to be fixed $value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed); return [ 'value' => $value, 'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags ]; } /** * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas. * Example: hello"world --> "hello""world". * * @param string $value UTF-8 encoded string * * @return string */ private static function UTF8toExcelDoubleQuoted($value) { return '"' . str_replace('"', '""', $value) . '"'; } /** * Reads first 8 bytes of a string and return IEEE 754 float. * * @param string $data Binary string that is at least 8 bytes long * * @return float */ private static function extractNumber($data) { $rknumhigh = self::getInt4d($data, 4); $rknumlow = self::getInt4d($data, 0); $sign = ($rknumhigh & (int) 0x80000000) >> 31; $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023; $mantissa = (0x100000 | ($rknumhigh & 0x000fffff)); $mantissalow1 = ($rknumlow & (int) 0x80000000) >> 31; $mantissalow2 = ($rknumlow & 0x7fffffff); $value = $mantissa / 2 ** (20 - $exp); if ($mantissalow1 != 0) { $value += 1 / 2 ** (21 - $exp); } $value += $mantissalow2 / 2 ** (52 - $exp); if ($sign) { $value *= -1; } return $value; } /** * @param int $rknum * * @return float */ private static function getIEEE754($rknum) { if (($rknum & 0x02) != 0) { $value = $rknum >> 2; } else { // changes by mmp, info on IEEE754 encoding from // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html // The RK format calls for using only the most significant 30 bits // of the 64 bit floating point value. The other 34 bits are assumed // to be 0 so we use the upper 30 bits of $rknum as follows... $sign = ($rknum & (int) 0x80000000) >> 31; $exp = ($rknum & 0x7ff00000) >> 20; $mantissa = (0x100000 | ($rknum & 0x000ffffc)); $value = $mantissa / 2 ** (20 - ($exp - 1023)); if ($sign) { $value = -1 * $value; } //end of changes by mmp } if (($rknum & 0x01) != 0) { $value /= 100; } return $value; } /** * Get UTF-8 string from (compressed or uncompressed) UTF-16 string. * * @param string $string * @param bool $compressed * * @return string */ private static function encodeUTF16($string, $compressed = false) { if ($compressed) { $string = self::uncompressByteString($string); } return StringHelper::convertEncoding($string, 'UTF-8', 'UTF-16LE'); } /** * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8. * * @param string $string * * @return string */ private static function uncompressByteString($string) { $uncompressedString = ''; $strLen = strlen($string); for ($i = 0; $i < $strLen; ++$i) { $uncompressedString .= $string[$i] . "\0"; } return $uncompressedString; } /** * Convert string to UTF-8. Only used for BIFF5. * * @param string $string * * @return string */ private function decodeCodepage($string) { return StringHelper::convertEncoding($string, 'UTF-8', $this->codepage); } /** * Read 16-bit unsigned integer. * * @param string $data * @param int $pos * * @return int */ public static function getUInt2d($data, $pos) { return ord($data[$pos]) | (ord($data[$pos + 1]) << 8); } /** * Read 16-bit signed integer. * * @param string $data * @param int $pos * * @return int */ public static function getInt2d($data, $pos) { return unpack('s', $data[$pos] . $data[$pos + 1])[1]; // @phpstan-ignore-line } /** * Read 32-bit signed integer. * * @param string $data * @param int $pos * * @return int */ public static function getInt4d($data, $pos) { // 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; } private function parseRichText(string $is): RichText { $value = new RichText(); $value->createText($is); return $value; } /** * Phpstan 1.4.4 complains that this property is never read. * So, we might be able to get rid of it altogether. * For now, however, this function makes it readable, * which satisfies Phpstan. * * @codeCoverageIgnore */ public function getMapCellStyleXfIndex(): array { return $this->mapCellStyleXfIndex; } private function readCFHeader(): array { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer forward to next record $this->pos += 4 + $length; if ($this->readDataOnly) { return []; } // offset: 0; size: 2; Rule Count // $ruleCount = self::getUInt2d($recordData, 0); // offset: var; size: var; cell range address list with $cellRangeAddressList = ($this->version == self::XLS_BIFF8) ? $this->readBIFF8CellRangeAddressList(substr($recordData, 12)) : $this->readBIFF5CellRangeAddressList(substr($recordData, 12)); $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses']; return $cellRangeAddresses; } private function readCFRule(array $cellRangeAddresses): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); // move stream pointer forward to next record $this->pos += 4 + $length; if ($this->readDataOnly) { return; } // offset: 0; size: 2; Options $cfRule = self::getUInt2d($recordData, 0); // bit: 8-15; mask: 0x00FF; type $type = (0x00FF & $cfRule) >> 0; $type = ConditionalFormatting::type($type); // bit: 0-7; mask: 0xFF00; type $operator = (0xFF00 & $cfRule) >> 8; $operator = ConditionalFormatting::operator($operator); if ($type === null || $operator === null) { return; } // offset: 2; size: 2; Size1 $size1 = self::getUInt2d($recordData, 2); // offset: 4; size: 2; Size2 $size2 = self::getUInt2d($recordData, 4); // offset: 6; size: 4; Options $options = self::getInt4d($recordData, 6); $style = new Style(false, true); // non-supervisor, conditional $this->getCFStyleOptions($options, $style); $hasFontRecord = (bool) ((0x04000000 & $options) >> 26); $hasAlignmentRecord = (bool) ((0x08000000 & $options) >> 27); $hasBorderRecord = (bool) ((0x10000000 & $options) >> 28); $hasFillRecord = (bool) ((0x20000000 & $options) >> 29); $hasProtectionRecord = (bool) ((0x40000000 & $options) >> 30); $offset = 12; if ($hasFontRecord === true) { $fontStyle = substr($recordData, $offset, 118); $this->getCFFontStyle($fontStyle, $style); $offset += 118; } if ($hasAlignmentRecord === true) { $alignmentStyle = substr($recordData, $offset, 8); $this->getCFAlignmentStyle($alignmentStyle, $style); $offset += 8; } if ($hasBorderRecord === true) { $borderStyle = substr($recordData, $offset, 8); $this->getCFBorderStyle($borderStyle, $style); $offset += 8; } if ($hasFillRecord === true) { $fillStyle = substr($recordData, $offset, 4); $this->getCFFillStyle($fillStyle, $style); $offset += 4; } if ($hasProtectionRecord === true) { $protectionStyle = substr($recordData, $offset, 4); $this->getCFProtectionStyle($protectionStyle, $style); $offset += 2; } $formula1 = $formula2 = null; if ($size1 > 0) { $formula1 = $this->readCFFormula($recordData, $offset, $size1); if ($formula1 === null) { return; } $offset += $size1; } if ($size2 > 0) { $formula2 = $this->readCFFormula($recordData, $offset, $size2); if ($formula2 === null) { return; } $offset += $size2; } $this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2, $style); } private function getCFStyleOptions(int $options, Style $style): void { } private function getCFFontStyle(string $options, Style $style): void { $fontSize = self::getInt4d($options, 64); if ($fontSize !== -1) { $style->getFont()->setSize($fontSize / 20); // Convert twips to points } $bold = self::getUInt2d($options, 72) === 700; // 400 = normal, 700 = bold $style->getFont()->setBold($bold); $color = self::getInt4d($options, 80); if ($color !== -1) { $style->getFont()->getColor()->setRGB(Xls\Color::map($color, $this->palette, $this->version)['rgb']); } } private function getCFAlignmentStyle(string $options, Style $style): void { } private function getCFBorderStyle(string $options, Style $style): void { } private function getCFFillStyle(string $options, Style $style): void { $fillPattern = self::getUInt2d($options, 0); // bit: 10-15; mask: 0xFC00; type $fillPattern = (0xFC00 & $fillPattern) >> 10; $fillPattern = FillPattern::lookup($fillPattern); $fillPattern = $fillPattern === Fill::FILL_NONE ? Fill::FILL_SOLID : $fillPattern; if ($fillPattern !== Fill::FILL_NONE) { $style->getFill()->setFillType($fillPattern); $fillColors = self::getUInt2d($options, 2); // bit: 0-6; mask: 0x007F; type $color1 = (0x007F & $fillColors) >> 0; $style->getFill()->getStartColor()->setRGB(Xls\Color::map($color1, $this->palette, $this->version)['rgb']); // bit: 7-13; mask: 0x3F80; type $color2 = (0x3F80 & $fillColors) >> 7; $style->getFill()->getEndColor()->setRGB(Xls\Color::map($color2, $this->palette, $this->version)['rgb']); } } private function getCFProtectionStyle(string $options, Style $style): void { } /** * @return null|float|int|string */ private function readCFFormula(string $recordData, int $offset, int $size) { try { $formula = substr($recordData, $offset, $size); $formula = pack('v', $size) . $formula; // prepend the length $formula = $this->getFormulaFromStructure($formula); if (is_numeric($formula)) { return (strpos($formula, '.') !== false) ? (float) $formula : (int) $formula; } return $formula; } catch (PhpSpreadsheetException $e) { } return null; } /** * @param null|float|int|string $formula1 * @param null|float|int|string $formula2 */ private function setCFRules(array $cellRanges, string $type, string $operator, $formula1, $formula2, Style $style): void { foreach ($cellRanges as $cellRange) { $conditional = new Conditional(); $conditional->setConditionType($type); $conditional->setOperatorType($operator); if ($formula1 !== null) { $conditional->addCondition($formula1); } if ($formula2 !== null) { $conditional->addCondition($formula2); } $conditional->setStyle($style); $conditionalStyles = $this->phpSheet->getStyle($cellRange)->getConditionalStyles(); $conditionalStyles[] = $conditional; $this->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles); } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php000064400000062605152427537250015771 0ustar00securityScanner = XmlScanner::getInstance($this); } /** @var string */ private $fileContents = ''; public static function xmlMappings(): array { return array_merge( Style\Fill::FILL_MAPPINGS, Style\Border::BORDER_MAPPINGS ); } /** * Can the current IReader read the file? */ public function canRead(string $filename): bool { // Office xmlns:o="urn:schemas-microsoft-com:office:office" // Excel xmlns:x="urn:schemas-microsoft-com:office:excel" // XML Spreadsheet xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" // Spreadsheet component xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet" // XML schema xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" // XML data type xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" // MS-persist recordset xmlns:rs="urn:schemas-microsoft-com:rowset" // Rowset xmlns:z="#RowsetSchema" // $signature = [ 'getSecurityScannerOrThrow()->scan($data); $valid = true; foreach ($signature as $match) { // every part of the signature must be present if (strpos($data, $match) === false) { $valid = false; break; } } $this->fileContents = $data; return $valid; } /** * Check if the file is a valid SimpleXML. * * @param string $filename * * @return false|SimpleXMLElement */ public function trySimpleXMLLoadString($filename) { try { $xml = simplexml_load_string( $this->getSecurityScannerOrThrow() ->scan($this->fileContents ?: file_get_contents($filename)) ); } catch (\Exception $e) { throw new Exception('Cannot load invalid XML file: ' . $filename, 0, $e); } $this->fileContents = ''; return $xml; } /** * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. * * @param string $filename * * @return array */ public function listWorksheetNames($filename) { File::assertFile($filename); if (!$this->canRead($filename)) { throw new Exception($filename . ' is an Invalid Spreadsheet file.'); } $worksheetNames = []; $xml = $this->trySimpleXMLLoadString($filename); if ($xml === false) { throw new Exception("Problem reading {$filename}"); } $xml_ss = $xml->children(self::NAMESPACES_SS); foreach ($xml_ss->Worksheet as $worksheet) { $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS); $worksheetNames[] = (string) $worksheet_ss['Name']; } return $worksheetNames; } /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * * @param string $filename * * @return array */ public function listWorksheetInfo($filename) { File::assertFile($filename); if (!$this->canRead($filename)) { throw new Exception($filename . ' is an Invalid Spreadsheet file.'); } $worksheetInfo = []; $xml = $this->trySimpleXMLLoadString($filename); if ($xml === false) { throw new Exception("Problem reading {$filename}"); } $worksheetID = 1; $xml_ss = $xml->children(self::NAMESPACES_SS); foreach ($xml_ss->Worksheet as $worksheet) { $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS); $tmpInfo = []; $tmpInfo['worksheetName'] = ''; $tmpInfo['lastColumnLetter'] = 'A'; $tmpInfo['lastColumnIndex'] = 0; $tmpInfo['totalRows'] = 0; $tmpInfo['totalColumns'] = 0; $tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}"; if (isset($worksheet_ss['Name'])) { $tmpInfo['worksheetName'] = (string) $worksheet_ss['Name']; } if (isset($worksheet->Table->Row)) { $rowIndex = 0; foreach ($worksheet->Table->Row as $rowData) { $columnIndex = 0; $rowHasData = false; foreach ($rowData->Cell as $cell) { if (isset($cell->Data)) { $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex); $rowHasData = true; } ++$columnIndex; } ++$rowIndex; if ($rowHasData) { $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex); } } } $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1; $worksheetInfo[] = $tmpInfo; ++$worksheetID; } return $worksheetInfo; } /** * Loads Spreadsheet from string. */ public function loadSpreadsheetFromString(string $contents): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); $spreadsheet->removeSheetByIndex(0); // Load into this instance return $this->loadIntoExisting($contents, $spreadsheet, true); } /** * Loads Spreadsheet from file. */ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); $spreadsheet->removeSheetByIndex(0); // Load into this instance return $this->loadIntoExisting($filename, $spreadsheet); } /** * Loads from file or contents into Spreadsheet instance. * * @param string $filename file name if useContents is false else file contents */ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, bool $useContents = false): Spreadsheet { if ($useContents) { $this->fileContents = $filename; } else { File::assertFile($filename); if (!$this->canRead($filename)) { throw new Exception($filename . ' is an Invalid Spreadsheet file.'); } } $xml = $this->trySimpleXMLLoadString($filename); if ($xml === false) { throw new Exception("Problem reading {$filename}"); } $namespaces = $xml->getNamespaces(true); (new Properties($spreadsheet))->readProperties($xml, $namespaces); $this->styles = (new Style())->parseStyles($xml, $namespaces); if (isset($this->styles['Default'])) { $spreadsheet->getCellXfCollection()[0]->applyFromArray($this->styles['Default']); } $worksheetID = 0; $xml_ss = $xml->children(self::NAMESPACES_SS); /** @var null|SimpleXMLElement $worksheetx */ foreach ($xml_ss->Worksheet as $worksheetx) { $worksheet = $worksheetx ?? new SimpleXMLElement(''); $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS); if ( isset($this->loadSheetsOnly, $worksheet_ss['Name']) && (!in_array($worksheet_ss['Name'], /** @scrutinizer ignore-type */ $this->loadSheetsOnly)) ) { continue; } // Create new Worksheet $spreadsheet->createSheet(); $spreadsheet->setActiveSheetIndex($worksheetID); $worksheetName = ''; if (isset($worksheet_ss['Name'])) { $worksheetName = (string) $worksheet_ss['Name']; // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in // formula cells... during the load, all formulae should be correct, and we're simply bringing // the worksheet name in line with the formula, not the reverse $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false); } if (isset($worksheet_ss['Protected'])) { $protection = (string) $worksheet_ss['Protected'] === '1'; $spreadsheet->getActiveSheet()->getProtection()->setSheet($protection); } // locally scoped defined names if (isset($worksheet->Names[0])) { foreach ($worksheet->Names[0] as $definedName) { $definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS); $name = (string) $definedName_ss['Name']; $definedValue = (string) $definedName_ss['RefersTo']; $convertedValue = AddressHelper::convertFormulaToA1($definedValue); if ($convertedValue[0] === '=') { $convertedValue = substr($convertedValue, 1); } $spreadsheet->addDefinedName(DefinedName::createInstance($name, $spreadsheet->getActiveSheet(), $convertedValue, true)); } } $columnID = 'A'; if (isset($worksheet->Table->Column)) { foreach ($worksheet->Table->Column as $columnData) { $columnData_ss = self::getAttributes($columnData, self::NAMESPACES_SS); $colspan = 0; if (isset($columnData_ss['Span'])) { $spanAttr = (string) $columnData_ss['Span']; if (is_numeric($spanAttr)) { $colspan = max(0, (int) $spanAttr); } } if (isset($columnData_ss['Index'])) { $columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']); } $columnWidth = null; if (isset($columnData_ss['Width'])) { $columnWidth = $columnData_ss['Width']; } $columnVisible = null; if (isset($columnData_ss['Hidden'])) { $columnVisible = ((string) $columnData_ss['Hidden']) !== '1'; } while ($colspan >= 0) { if (isset($columnWidth)) { $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4); } if (isset($columnVisible)) { $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setVisible($columnVisible); } ++$columnID; --$colspan; } } } $rowID = 1; if (isset($worksheet->Table->Row)) { $additionalMergedCells = 0; foreach ($worksheet->Table->Row as $rowData) { $rowHasData = false; $row_ss = self::getAttributes($rowData, self::NAMESPACES_SS); if (isset($row_ss['Index'])) { $rowID = (int) $row_ss['Index']; } if (isset($row_ss['Hidden'])) { $rowVisible = ((string) $row_ss['Hidden']) !== '1'; $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setVisible($rowVisible); } $columnID = 'A'; foreach ($rowData->Cell as $cell) { $cell_ss = self::getAttributes($cell, self::NAMESPACES_SS); if (isset($cell_ss['Index'])) { $columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']); } $cellRange = $columnID . $rowID; if ($this->getReadFilter() !== null) { if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) { ++$columnID; continue; } } if (isset($cell_ss['HRef'])) { $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl((string) $cell_ss['HRef']); } if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) { $columnTo = $columnID; if (isset($cell_ss['MergeAcross'])) { $additionalMergedCells += (int) $cell_ss['MergeAcross']; $columnTo = Coordinate::stringFromColumnIndex((int) (Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross'])); } $rowTo = $rowID; if (isset($cell_ss['MergeDown'])) { $rowTo = $rowTo + $cell_ss['MergeDown']; } $cellRange .= ':' . $columnTo . $rowTo; $spreadsheet->getActiveSheet()->mergeCells($cellRange, Worksheet::MERGE_CELL_CONTENT_HIDE); } $hasCalculatedValue = false; $cellDataFormula = ''; if (isset($cell_ss['Formula'])) { $cellDataFormula = $cell_ss['Formula']; $hasCalculatedValue = true; } if (isset($cell->Data)) { $cellData = $cell->Data; $cellValue = (string) $cellData; $type = DataType::TYPE_NULL; $cellData_ss = self::getAttributes($cellData, self::NAMESPACES_SS); if (isset($cellData_ss['Type'])) { $cellDataType = $cellData_ss['Type']; switch ($cellDataType) { /* const TYPE_STRING = 's'; const TYPE_FORMULA = 'f'; const TYPE_NUMERIC = 'n'; const TYPE_BOOL = 'b'; const TYPE_NULL = 'null'; const TYPE_INLINE = 'inlineStr'; const TYPE_ERROR = 'e'; */ case 'String': $type = DataType::TYPE_STRING; break; case 'Number': $type = DataType::TYPE_NUMERIC; $cellValue = (float) $cellValue; if (floor($cellValue) == $cellValue) { $cellValue = (int) $cellValue; } break; case 'Boolean': $type = DataType::TYPE_BOOL; $cellValue = ($cellValue != 0); break; case 'DateTime': $type = DataType::TYPE_NUMERIC; $dateTime = new DateTime($cellValue, new DateTimeZone('UTC')); $cellValue = Date::PHPToExcel($dateTime); break; case 'Error': $type = DataType::TYPE_ERROR; $hasCalculatedValue = false; break; } } if ($hasCalculatedValue) { $type = DataType::TYPE_FORMULA; $columnNumber = Coordinate::columnIndexFromString($columnID); $cellDataFormula = AddressHelper::convertFormulaToA1($cellDataFormula, $rowID, $columnNumber); } $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type); if ($hasCalculatedValue) { $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue); } $rowHasData = true; } if (isset($cell->Comment)) { $this->parseCellComment($cell->Comment, $spreadsheet, $columnID, $rowID); } if (isset($cell_ss['StyleID'])) { $style = (string) $cell_ss['StyleID']; if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) { //if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) { // $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null); //} $spreadsheet->getActiveSheet()->getStyle($cellRange) ->applyFromArray($this->styles[$style]); } } ++$columnID; while ($additionalMergedCells > 0) { ++$columnID; --$additionalMergedCells; } } if ($rowHasData) { if (isset($row_ss['Height'])) { $rowHeight = $row_ss['Height']; $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight((float) $rowHeight); } } ++$rowID; } } $dataValidations = new Xml\DataValidations(); $dataValidations->loadDataValidations($worksheet, $spreadsheet); $xmlX = $worksheet->children(Namespaces::URN_EXCEL); if (isset($xmlX->WorksheetOptions)) { if (isset($xmlX->WorksheetOptions->FreezePanes)) { $freezeRow = $freezeColumn = 1; if (isset($xmlX->WorksheetOptions->SplitHorizontal)) { $freezeRow = (int) $xmlX->WorksheetOptions->SplitHorizontal + 1; } if (isset($xmlX->WorksheetOptions->SplitVertical)) { $freezeColumn = (int) $xmlX->WorksheetOptions->SplitVertical + 1; } $spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow); } (new PageSettings($xmlX))->loadPageSettings($spreadsheet); if (isset($xmlX->WorksheetOptions->TopRowVisible, $xmlX->WorksheetOptions->LeftColumnVisible)) { $leftTopRow = (string) $xmlX->WorksheetOptions->TopRowVisible; $leftTopColumn = (string) $xmlX->WorksheetOptions->LeftColumnVisible; if (is_numeric($leftTopRow) && is_numeric($leftTopColumn)) { $leftTopCoordinate = Coordinate::stringFromColumnIndex((int) $leftTopColumn + 1) . (string) ($leftTopRow + 1); $spreadsheet->getActiveSheet()->setTopLeftCell($leftTopCoordinate); } } $rangeCalculated = false; if (isset($xmlX->WorksheetOptions->Panes->Pane->RangeSelection)) { if (1 === preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $xmlX->WorksheetOptions->Panes->Pane->RangeSelection, $selectionMatches)) { $selectedCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2]) . $selectionMatches[1] . ':' . Coordinate::stringFromColumnIndex((int) $selectionMatches[4]) . $selectionMatches[3]; $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell); $rangeCalculated = true; } } if (!$rangeCalculated) { if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveRow)) { $activeRow = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveRow; } else { $activeRow = 0; } if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveCol)) { $activeColumn = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveCol; } else { $activeColumn = 0; } if (is_numeric($activeRow) && is_numeric($activeColumn)) { $selectedCell = Coordinate::stringFromColumnIndex((int) $activeColumn + 1) . (string) ($activeRow + 1); $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell); } } } ++$worksheetID; } // Globally scoped defined names $activeSheetIndex = 0; if (isset($xml->ExcelWorkbook->ActiveSheet)) { $activeSheetIndex = (int) (string) $xml->ExcelWorkbook->ActiveSheet; } $activeWorksheet = $spreadsheet->setActiveSheetIndex($activeSheetIndex); if (isset($xml->Names[0])) { foreach ($xml->Names[0] as $definedName) { $definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS); $name = (string) $definedName_ss['Name']; $definedValue = (string) $definedName_ss['RefersTo']; $convertedValue = AddressHelper::convertFormulaToA1($definedValue); if ($convertedValue[0] === '=') { $convertedValue = substr($convertedValue, 1); } $spreadsheet->addDefinedName(DefinedName::createInstance($name, $activeWorksheet, $convertedValue)); } } // Return return $spreadsheet; } protected function parseCellComment( SimpleXMLElement $comment, Spreadsheet $spreadsheet, string $columnID, int $rowID ): void { $commentAttributes = $comment->attributes(self::NAMESPACES_SS); $author = 'unknown'; if (isset($commentAttributes->Author)) { $author = (string) $commentAttributes->Author; } $node = $comment->Data->asXML(); $annotation = strip_tags((string) $node); $spreadsheet->getActiveSheet()->getComment($columnID . $rowID) ->setAuthor($author) ->setText($this->parseRichText($annotation)); } protected function parseRichText(string $annotation): RichText { $value = new RichText(); $value->createText($annotation); return $value; } private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement { return ($simple === null) ? new SimpleXMLElement('') : ($simple->attributes($node) ?? new SimpleXMLElement('')); } } phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php000064400000012263152427537250017221 0ustar00readFilter = new DefaultReadFilter(); } public function getReadDataOnly() { return $this->readDataOnly; } public function setReadDataOnly($readCellValuesOnly) { $this->readDataOnly = (bool) $readCellValuesOnly; return $this; } public function getReadEmptyCells() { return $this->readEmptyCells; } public function setReadEmptyCells($readEmptyCells) { $this->readEmptyCells = (bool) $readEmptyCells; return $this; } public function getIncludeCharts() { return $this->includeCharts; } public function setIncludeCharts($includeCharts) { $this->includeCharts = (bool) $includeCharts; return $this; } public function getLoadSheetsOnly() { return $this->loadSheetsOnly; } public function setLoadSheetsOnly($sheetList) { if ($sheetList === null) { return $this->setLoadAllSheets(); } $this->loadSheetsOnly = is_array($sheetList) ? $sheetList : [$sheetList]; return $this; } public function setLoadAllSheets() { $this->loadSheetsOnly = null; return $this; } public function getReadFilter() { return $this->readFilter; } public function setReadFilter(IReadFilter $readFilter) { $this->readFilter = $readFilter; return $this; } public function getSecurityScanner(): ?XmlScanner { return $this->securityScanner; } public function getSecurityScannerOrThrow(): XmlScanner { if ($this->securityScanner === null) { throw new ReaderException('Security scanner is unexpectedly null'); } return $this->securityScanner; } protected function processFlags(int $flags): void { if (((bool) ($flags & self::LOAD_WITH_CHARTS)) === true) { $this->setIncludeCharts(true); } if (((bool) ($flags & self::READ_DATA_ONLY)) === true) { $this->setReadDataOnly(true); } if (((bool) ($flags & self::SKIP_EMPTY_CELLS) || (bool) ($flags & self::IGNORE_EMPTY_CELLS)) === true) { $this->setReadEmptyCells(false); } } protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { throw new PhpSpreadsheetException('Reader classes must implement their own loadSpreadsheetFromFile() method'); } /** * Loads Spreadsheet from file. * * @param int $flags the optional second parameter flags may be used to identify specific elements * that should be loaded, but which won't be loaded by default, using these values: * IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file */ public function load(string $filename, int $flags = 0): Spreadsheet { $this->processFlags($flags); try { return $this->loadSpreadsheetFromFile($filename); } catch (ReaderException $e) { throw $e; } } /** * Open file for reading. */ protected function openFile(string $filename): void { $fileHandle = false; if ($filename) { File::assertFile($filename); // Open file $fileHandle = fopen($filename, 'rb'); } if ($fileHandle === false) { throw new ReaderException('Could not open file ' . $filename . ' for reading.'); } $this->fileHandle = $fileHandle; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php000064400000404626152427537250016172 0ustar00referenceHelper = ReferenceHelper::getInstance(); $this->securityScanner = XmlScanner::getInstance($this); } /** * Can the current IReader read the file? */ public function canRead(string $filename): bool { if (!File::testFileNoThrow($filename, self::INITIAL_FILE)) { return false; } $result = false; $this->zip = $zip = new ZipArchive(); if ($zip->open($filename) === true) { [$workbookBasename] = $this->getWorkbookBaseName(); $result = !empty($workbookBasename); $zip->close(); } return $result; } /** * @param mixed $value */ public static function testSimpleXml($value): SimpleXMLElement { return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement(''); } public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement { return self::testSimpleXml($value === null ? $value : $value->attributes($ns)); } // Phpstan thinks, correctly, that xpath can return false. // Scrutinizer thinks it can't. // Sigh. private static function xpathNoFalse(SimpleXmlElement $sxml, string $path): array { return self::falseToArray($sxml->xpath($path)); } /** * @param mixed $value */ public static function falseToArray($value): array { return is_array($value) ? $value : []; } private function loadZip(string $filename, string $ns = '', bool $replaceUnclosedBr = false): SimpleXMLElement { $contents = $this->getFromZipArchive($this->zip, $filename); if ($replaceUnclosedBr) { $contents = str_replace('
', '
', $contents); } $rels = @simplexml_load_string( $this->getSecurityScannerOrThrow()->scan($contents), 'SimpleXMLElement', 0, $ns ); return self::testSimpleXml($rels); } // This function is just to identify cases where I'm not sure // why empty namespace is required. private function loadZipNonamespace(string $filename, string $ns): SimpleXMLElement { $contents = $this->getFromZipArchive($this->zip, $filename); $rels = simplexml_load_string( $this->getSecurityScannerOrThrow()->scan($contents), 'SimpleXMLElement', 0, ($ns === '' ? $ns : '') ); return self::testSimpleXml($rels); } private const REL_TO_MAIN = [ Namespaces::PURL_OFFICE_DOCUMENT => Namespaces::PURL_MAIN, Namespaces::THUMBNAIL => '', ]; private const REL_TO_DRAWING = [ Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING, ]; private const REL_TO_CHART = [ Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_CHART, ]; /** * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. * * @param string $filename * * @return array */ public function listWorksheetNames($filename) { File::assertFile($filename, self::INITIAL_FILE); $worksheetNames = []; $this->zip = $zip = new ZipArchive(); $zip->open($filename); // The files we're looking at here are small enough that simpleXML is more efficient than XMLReader $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS); foreach ($rels->Relationship as $relx) { $rel = self::getAttributes($relx); $relType = (string) $rel['Type']; $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; if ($mainNS !== '') { $xmlWorkbook = $this->loadZip((string) $rel['Target'], $mainNS); if ($xmlWorkbook->sheets) { foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { // Check if sheet should be skipped $worksheetNames[] = (string) self::getAttributes($eleSheet)['name']; } } } } $zip->close(); return $worksheetNames; } /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * * @param string $filename * * @return array */ public function listWorksheetInfo($filename) { File::assertFile($filename, self::INITIAL_FILE); $worksheetInfo = []; $this->zip = $zip = new ZipArchive(); $zip->open($filename); $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS); foreach ($rels->Relationship as $relx) { $rel = self::getAttributes($relx); $relType = (string) $rel['Type']; $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; if ($mainNS !== '') { $relTarget = (string) $rel['Target']; $dir = dirname($relTarget); $namespace = dirname($relType); $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', ''); $worksheets = []; foreach ($relsWorkbook->Relationship as $elex) { $ele = self::getAttributes($elex); if ( ((string) $ele['Type'] === "$namespace/worksheet") || ((string) $ele['Type'] === "$namespace/chartsheet") ) { $worksheets[(string) $ele['Id']] = $ele['Target']; } } $xmlWorkbook = $this->loadZip($relTarget, $mainNS); if ($xmlWorkbook->sheets) { $dir = dirname($relTarget); /** @var SimpleXMLElement $eleSheet */ foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { $tmpInfo = [ 'worksheetName' => (string) self::getAttributes($eleSheet)['name'], 'lastColumnLetter' => 'A', 'lastColumnIndex' => 0, 'totalRows' => 0, 'totalColumns' => 0, ]; $fileWorksheet = (string) $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $namespace), 'id')]; $fileWorksheetPath = strpos($fileWorksheet, '/') === 0 ? substr($fileWorksheet, 1) : "$dir/$fileWorksheet"; $xml = new XMLReader(); $xml->xml( $this->getSecurityScannerOrThrow() ->scan( $this->getFromZipArchive( $this->zip, $fileWorksheetPath ) ) ); $xml->setParserProperty(2, true); $currCells = 0; while ($xml->read()) { if ($xml->localName == 'row' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) { $row = $xml->getAttribute('r'); $tmpInfo['totalRows'] = $row; $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); $currCells = 0; } elseif ($xml->localName == 'c' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) { $cell = $xml->getAttribute('r'); $currCells = $cell ? max($currCells, Coordinate::indexesFromString($cell)[0]) : ($currCells + 1); } } $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); $xml->close(); $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1; $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); $worksheetInfo[] = $tmpInfo; } } } } $zip->close(); return $worksheetInfo; } private static function castToBoolean(SimpleXMLElement $c): bool { $value = isset($c->v) ? (string) $c->v : null; if ($value == '0') { return false; } elseif ($value == '1') { return true; } return (bool) $c->v; } private static function castToError(?SimpleXMLElement $c): ?string { return isset($c, $c->v) ? (string) $c->v : null; } private static function castToString(?SimpleXMLElement $c): ?string { return isset($c, $c->v) ? (string) $c->v : null; } /** * @param mixed $value * @param mixed $calculatedValue */ private function castToFormula(?SimpleXMLElement $c, string $r, string &$cellDataType, &$value, &$calculatedValue, string $castBaseType, bool $updateSharedCells = true): void { if ($c === null) { return; } $attr = $c->f->attributes(); $cellDataType = DataType::TYPE_FORMULA; $value = "={$c->f}"; $calculatedValue = self::$castBaseType($c); // Shared formula? if (isset($attr['t']) && strtolower((string) $attr['t']) == 'shared') { $instance = (string) $attr['si']; if (!isset($this->sharedFormulae[(string) $attr['si']])) { $this->sharedFormulae[$instance] = new SharedFormula($r, $value); } elseif ($updateSharedCells === true) { // It's only worth the overhead of adjusting the shared formula for this cell if we're actually loading // the cell, which may not be the case if we're using a read filter. $master = Coordinate::indexesFromString($this->sharedFormulae[$instance]->master()); $current = Coordinate::indexesFromString($r); $difference = [0, 0]; $difference[0] = $current[0] - $master[0]; $difference[1] = $current[1] - $master[1]; $value = $this->referenceHelper->updateFormulaReferences($this->sharedFormulae[$instance]->formula(), 'A1', $difference[0], $difference[1]); } } } /** * @param string $fileName */ private function fileExistsInArchive(ZipArchive $archive, $fileName = ''): bool { // Root-relative paths if (strpos($fileName, '//') !== false) { $fileName = substr($fileName, strpos($fileName, '//') + 1); } $fileName = File::realpath($fileName); // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming // so we need to load case-insensitively from the zip file // Apache POI fixes $contents = $archive->locateName($fileName, ZipArchive::FL_NOCASE); if ($contents === false) { $contents = $archive->locateName(substr($fileName, 1), ZipArchive::FL_NOCASE); } return $contents !== false; } /** * @param string $fileName * * @return string */ private function getFromZipArchive(ZipArchive $archive, $fileName = '') { // Root-relative paths if (strpos($fileName, '//') !== false) { $fileName = substr($fileName, strpos($fileName, '//') + 1); } // Relative paths generated by dirname($filename) when $filename // has no path (i.e.files in root of the zip archive) $fileName = (string) preg_replace('/^\.\//', '', $fileName); $fileName = File::realpath($fileName); // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming // so we need to load case-insensitively from the zip file $contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE); // Apache POI fixes if ($contents === false) { $contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE); } // Has the file been saved with Windoze directory separators rather than unix? if ($contents === false) { $contents = $archive->getFromName(str_replace('/', '\\', $fileName), 0, ZipArchive::FL_NOCASE); } return ($contents === false) ? '' : $contents; } /** * Loads Spreadsheet from file. */ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { File::assertFile($filename, self::INITIAL_FILE); // Initialisations $excel = new Spreadsheet(); $excel->removeSheetByIndex(0); $addingFirstCellStyleXf = true; $addingFirstCellXf = true; $unparsedLoadedData = []; $this->zip = $zip = new ZipArchive(); $zip->open($filename); // Read the theme first, because we need the colour scheme when reading the styles [$workbookBasename, $xmlNamespaceBase] = $this->getWorkbookBaseName(); $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML; $chartNS = self::REL_TO_CHART[$xmlNamespaceBase] ?? Namespaces::CHART; $wbRels = $this->loadZip("xl/_rels/{$workbookBasename}.rels", Namespaces::RELATIONSHIPS); $theme = null; $this->styleReader = new Styles(); foreach ($wbRels->Relationship as $relx) { $rel = self::getAttributes($relx); $relTarget = (string) $rel['Target']; if (substr($relTarget, 0, 4) === '/xl/') { $relTarget = substr($relTarget, 4); } switch ($rel['Type']) { case "$xmlNamespaceBase/theme": $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2']; $themeOrderAdditional = count($themeOrderArray); $xmlTheme = $this->loadZip("xl/{$relTarget}", $drawingNS); $xmlThemeName = self::getAttributes($xmlTheme); $xmlTheme = $xmlTheme->children($drawingNS); $themeName = (string) $xmlThemeName['name']; $colourScheme = self::getAttributes($xmlTheme->themeElements->clrScheme); $colourSchemeName = (string) $colourScheme['name']; $excel->getTheme()->setThemeColorName($colourSchemeName); $colourScheme = $xmlTheme->themeElements->clrScheme->children($drawingNS); $themeColours = []; foreach ($colourScheme as $k => $xmlColour) { $themePos = array_search($k, $themeOrderArray); if ($themePos === false) { $themePos = $themeOrderAdditional++; } if (isset($xmlColour->sysClr)) { $xmlColourData = self::getAttributes($xmlColour->sysClr); $themeColours[$themePos] = (string) $xmlColourData['lastClr']; $excel->getTheme()->setThemeColor($k, (string) $xmlColourData['lastClr']); } elseif (isset($xmlColour->srgbClr)) { $xmlColourData = self::getAttributes($xmlColour->srgbClr); $themeColours[$themePos] = (string) $xmlColourData['val']; $excel->getTheme()->setThemeColor($k, (string) $xmlColourData['val']); } } $theme = new Theme($themeName, $colourSchemeName, $themeColours); $this->styleReader->setTheme($theme); $fontScheme = self::getAttributes($xmlTheme->themeElements->fontScheme); $fontSchemeName = (string) $fontScheme['name']; $excel->getTheme()->setThemeFontName($fontSchemeName); $majorFonts = []; $minorFonts = []; $fontScheme = $xmlTheme->themeElements->fontScheme->children($drawingNS); $majorLatin = self::getAttributes($fontScheme->majorFont->latin)['typeface'] ?? ''; $majorEastAsian = self::getAttributes($fontScheme->majorFont->ea)['typeface'] ?? ''; $majorComplexScript = self::getAttributes($fontScheme->majorFont->cs)['typeface'] ?? ''; $minorLatin = self::getAttributes($fontScheme->minorFont->latin)['typeface'] ?? ''; $minorEastAsian = self::getAttributes($fontScheme->minorFont->ea)['typeface'] ?? ''; $minorComplexScript = self::getAttributes($fontScheme->minorFont->cs)['typeface'] ?? ''; foreach ($fontScheme->majorFont->font as $xmlFont) { $fontAttributes = self::getAttributes($xmlFont); $script = (string) ($fontAttributes['script'] ?? ''); if (!empty($script)) { $majorFonts[$script] = (string) ($fontAttributes['typeface'] ?? ''); } } foreach ($fontScheme->minorFont->font as $xmlFont) { $fontAttributes = self::getAttributes($xmlFont); $script = (string) ($fontAttributes['script'] ?? ''); if (!empty($script)) { $minorFonts[$script] = (string) ($fontAttributes['typeface'] ?? ''); } } $excel->getTheme()->setMajorFontValues($majorLatin, $majorEastAsian, $majorComplexScript, $majorFonts); $excel->getTheme()->setMinorFontValues($minorLatin, $minorEastAsian, $minorComplexScript, $minorFonts); break; } } $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS); $propertyReader = new PropertyReader($this->getSecurityScannerOrThrow(), $excel->getProperties()); $chartDetails = []; foreach ($rels->Relationship as $relx) { $rel = self::getAttributes($relx); $relTarget = (string) $rel['Target']; // issue 3553 if ($relTarget[0] === '/') { $relTarget = substr($relTarget, 1); } $relType = (string) $rel['Type']; $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; switch ($relType) { case Namespaces::CORE_PROPERTIES: $propertyReader->readCoreProperties($this->getFromZipArchive($zip, $relTarget)); break; case "$xmlNamespaceBase/extended-properties": $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, $relTarget)); break; case "$xmlNamespaceBase/custom-properties": $propertyReader->readCustomProperties($this->getFromZipArchive($zip, $relTarget)); break; //Ribbon case Namespaces::EXTENSIBILITY: $customUI = $relTarget; if ($customUI) { $this->readRibbon($excel, $customUI, $zip); } break; case "$xmlNamespaceBase/officeDocument": $dir = dirname($relTarget); // Do not specify namespace in next stmt - do it in Xpath $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', ''); $relsWorkbook->registerXPathNamespace('rel', Namespaces::RELATIONSHIPS); $worksheets = []; $macros = $customUI = null; foreach ($relsWorkbook->Relationship as $elex) { $ele = self::getAttributes($elex); switch ($ele['Type']) { case Namespaces::WORKSHEET: case Namespaces::PURL_WORKSHEET: $worksheets[(string) $ele['Id']] = $ele['Target']; break; case Namespaces::CHARTSHEET: if ($this->includeCharts === true) { $worksheets[(string) $ele['Id']] = $ele['Target']; } break; // a vbaProject ? (: some macros) case Namespaces::VBA: $macros = $ele['Target']; break; } } if ($macros !== null) { $macrosCode = $this->getFromZipArchive($zip, 'xl/vbaProject.bin'); //vbaProject.bin always in 'xl' dir and always named vbaProject.bin if ($macrosCode !== false) { $excel->setMacrosCode($macrosCode); $excel->setHasMacros(true); //short-circuit : not reading vbaProject.bin.rel to get Signature =>allways vbaProjectSignature.bin in 'xl' dir $Certificate = $this->getFromZipArchive($zip, 'xl/vbaProjectSignature.bin'); if ($Certificate !== false) { $excel->setMacrosCertificate($Certificate); } } } $relType = "rel:Relationship[@Type='" . "$xmlNamespaceBase/styles" . "']"; $xpath = self::getArrayItem(self::xpathNoFalse($relsWorkbook, $relType)); if ($xpath === null) { $xmlStyles = self::testSimpleXml(null); } else { $xmlStyles = $this->loadZip("$dir/$xpath[Target]", $mainNS); } $palette = self::extractPalette($xmlStyles); $this->styleReader->setWorkbookPalette($palette); $fills = self::extractStyles($xmlStyles, 'fills', 'fill'); $fonts = self::extractStyles($xmlStyles, 'fonts', 'font'); $borders = self::extractStyles($xmlStyles, 'borders', 'border'); $xfTags = self::extractStyles($xmlStyles, 'cellXfs', 'xf'); $cellXfTags = self::extractStyles($xmlStyles, 'cellStyleXfs', 'xf'); $styles = []; $cellStyles = []; $numFmts = null; if (/*$xmlStyles && */ $xmlStyles->numFmts[0]) { $numFmts = $xmlStyles->numFmts[0]; } if (isset($numFmts) && ($numFmts !== null)) { $numFmts->registerXPathNamespace('sml', $mainNS); } $this->styleReader->setNamespace($mainNS); if (!$this->readDataOnly/* && $xmlStyles*/) { foreach ($xfTags as $xfTag) { $xf = self::getAttributes($xfTag); $numFmt = null; if ($xf['numFmtId']) { if (isset($numFmts)) { $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]")); if (isset($tmpNumFmt['formatCode'])) { $numFmt = (string) $tmpNumFmt['formatCode']; } } // We shouldn't override any of the built-in MS Excel values (values below id 164) // But there's a lot of naughty homebrew xlsx writers that do use "reserved" id values that aren't actually used // So we make allowance for them rather than lose formatting masks if ( $numFmt === null && (int) $xf['numFmtId'] < 164 && NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== '' ) { $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']); } } $quotePrefix = (bool) (string) ($xf['quotePrefix'] ?? ''); $style = (object) [ 'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL, 'font' => $fonts[(int) ($xf['fontId'])], 'fill' => $fills[(int) ($xf['fillId'])], 'border' => $borders[(int) ($xf['borderId'])], 'alignment' => $xfTag->alignment, 'protection' => $xfTag->protection, 'quotePrefix' => $quotePrefix, ]; $styles[] = $style; // add style to cellXf collection $objStyle = new Style(); $this->styleReader->readStyle($objStyle, $style); if ($addingFirstCellXf) { $excel->removeCellXfByIndex(0); // remove the default style $addingFirstCellXf = false; } $excel->addCellXf($objStyle); } foreach ($cellXfTags as $xfTag) { $xf = self::getAttributes($xfTag); $numFmt = NumberFormat::FORMAT_GENERAL; if ($numFmts && $xf['numFmtId']) { $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]")); if (isset($tmpNumFmt['formatCode'])) { $numFmt = (string) $tmpNumFmt['formatCode']; } elseif ((int) $xf['numFmtId'] < 165) { $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']); } } $quotePrefix = (bool) (string) ($xf['quotePrefix'] ?? ''); $cellStyle = (object) [ 'numFmt' => $numFmt, 'font' => $fonts[(int) ($xf['fontId'])], 'fill' => $fills[((int) $xf['fillId'])], 'border' => $borders[(int) ($xf['borderId'])], 'alignment' => $xfTag->alignment, 'protection' => $xfTag->protection, 'quotePrefix' => $quotePrefix, ]; $cellStyles[] = $cellStyle; // add style to cellStyleXf collection $objStyle = new Style(); $this->styleReader->readStyle($objStyle, $cellStyle); if ($addingFirstCellStyleXf) { $excel->removeCellStyleXfByIndex(0); // remove the default style $addingFirstCellStyleXf = false; } $excel->addCellStyleXf($objStyle); } } $this->styleReader->setStyleXml($xmlStyles); $this->styleReader->setNamespace($mainNS); $this->styleReader->setStyleBaseData($theme, $styles, $cellStyles); $dxfs = $this->styleReader->dxfs($this->readDataOnly); $styles = $this->styleReader->styles(); // Read content after setting the styles $sharedStrings = []; $relType = "rel:Relationship[@Type='" //. Namespaces::SHARED_STRINGS . "$xmlNamespaceBase/sharedStrings" . "']"; $xpath = self::getArrayItem($relsWorkbook->xpath($relType)); if ($xpath) { $xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS); if (isset($xmlStrings->si)) { foreach ($xmlStrings->si as $val) { if (isset($val->t)) { $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t); } elseif (isset($val->r)) { $sharedStrings[] = $this->parseRichText($val); } } } } $xmlWorkbook = $this->loadZipNoNamespace($relTarget, $mainNS); $xmlWorkbookNS = $this->loadZip($relTarget, $mainNS); // Set base date if ($xmlWorkbookNS->workbookPr) { Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); $attrs1904 = self::getAttributes($xmlWorkbookNS->workbookPr); if (isset($attrs1904['date1904'])) { if (self::boolean((string) $attrs1904['date1904'])) { Date::setExcelCalendar(Date::CALENDAR_MAC_1904); } } } // Set protection $this->readProtection($excel, $xmlWorkbook); $sheetId = 0; // keep track of new sheet id in final workbook $oldSheetId = -1; // keep track of old sheet id in final workbook $countSkippedSheets = 0; // keep track of number of skipped sheets $mapSheetId = []; // mapping of sheet ids from old to new $charts = $chartDetails = []; if ($xmlWorkbookNS->sheets) { /** @var SimpleXMLElement $eleSheet */ foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) { $eleSheetAttr = self::getAttributes($eleSheet); ++$oldSheetId; // Check if sheet should be skipped if (is_array($this->loadSheetsOnly) && !in_array((string) $eleSheetAttr['name'], $this->loadSheetsOnly)) { ++$countSkippedSheets; $mapSheetId[$oldSheetId] = null; continue; } $sheetReferenceId = (string) self::getArrayItem(self::getAttributes($eleSheet, $xmlNamespaceBase), 'id'); if (isset($worksheets[$sheetReferenceId]) === false) { ++$countSkippedSheets; $mapSheetId[$oldSheetId] = null; continue; } // Map old sheet id in original workbook to new sheet id. // They will differ if loadSheetsOnly() is being used $mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets; // Load sheet $docSheet = $excel->createSheet(); // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet // references in formula cells... during the load, all formulae should be correct, // and we're simply bringing the worksheet name in line with the formula, not the // reverse $docSheet->setTitle((string) $eleSheetAttr['name'], false, false); $fileWorksheet = (string) $worksheets[$sheetReferenceId]; $xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS); $xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS); // Shared Formula table is unique to each Worksheet, so we need to reset it here $this->sharedFormulae = []; if (isset($eleSheetAttr['state']) && (string) $eleSheetAttr['state'] != '') { $docSheet->setSheetState((string) $eleSheetAttr['state']); } if ($xmlSheetNS) { $xmlSheetMain = $xmlSheetNS->children($mainNS); // Setting Conditional Styles adjusts selected cells, so we need to execute this // before reading the sheet view data to get the actual selected cells if (!$this->readDataOnly && ($xmlSheet->conditionalFormatting)) { (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load(); } if (!$this->readDataOnly && $xmlSheet->extLst) { (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->loadFromExt($this->styleReader); } if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) { $sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet); $sheetViews->load(); } $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheetNS); $sheetViewOptions->load($this->getReadDataOnly(), $this->styleReader); (new ColumnAndRowAttributes($docSheet, $xmlSheetNS)) ->load($this->getReadFilter(), $this->getReadDataOnly()); } if ($xmlSheetNS && $xmlSheetNS->sheetData && $xmlSheetNS->sheetData->row) { $cIndex = 1; // Cell Start from 1 foreach ($xmlSheetNS->sheetData->row as $row) { $rowIndex = 1; foreach ($row->c as $c) { $cAttr = self::getAttributes($c); $r = (string) $cAttr['r']; if ($r == '') { $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex; } $cellDataType = (string) $cAttr['t']; $value = null; $calculatedValue = null; // Read cell? if ($this->getReadFilter() !== null) { $coordinates = Coordinate::coordinateFromString($r); if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) { // Normally, just testing for the f attribute should identify this cell as containing a formula // that we need to read, even though it is outside of the filter range, in case it is a shared formula. // But in some cases, this attribute isn't set; so we need to delve a level deeper and look at // whether or not the cell has a child formula element that is shared. if (isset($cAttr->f) || (isset($c->f, $c->f->attributes()['t']) && strtolower((string) $c->f->attributes()['t']) === 'shared')) { $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError', false); } ++$rowIndex; continue; } } // Read cell! switch ($cellDataType) { case 's': if ((string) $c->v != '') { $value = $sharedStrings[(int) ($c->v)]; if ($value instanceof RichText) { $value = clone $value; } } else { $value = ''; } break; case 'b': if (!isset($c->f)) { if (isset($c->v)) { $value = self::castToBoolean($c); } else { $value = null; $cellDataType = DATATYPE::TYPE_NULL; } } else { // Formula $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToBoolean'); if (isset($c->f['t'])) { $att = $c->f; $docSheet->getCell($r)->setFormulaAttributes($att); } } break; case 'inlineStr': if (isset($c->f)) { $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError'); } else { $value = $this->parseRichText($c->is); } break; case 'e': if (!isset($c->f)) { $value = self::castToError($c); } else { // Formula $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError'); } break; default: if (!isset($c->f)) { $value = self::castToString($c); } else { // Formula $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToString'); if (isset($c->f['t'])) { $attributes = $c->f['t']; $docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]); } } break; } // read empty cells or the cells are not empty if ($this->readEmptyCells || ($value !== null && $value !== '')) { // Rich text? if ($value instanceof RichText && $this->readDataOnly) { $value = $value->getPlainText(); } $cell = $docSheet->getCell($r); // Assign value if ($cellDataType != '') { // it is possible, that datatype is numeric but with an empty string, which result in an error if ($cellDataType === DataType::TYPE_NUMERIC && ($value === '' || $value === null)) { $cellDataType = DataType::TYPE_NULL; } if ($cellDataType !== DataType::TYPE_NULL) { $cell->setValueExplicit($value, $cellDataType); } } else { $cell->setValue($value); } if ($calculatedValue !== null) { $cell->setCalculatedValue($calculatedValue); } // Style information? if ($cAttr['s'] && !$this->readDataOnly) { // no style index means 0, it seems $cell->setXfIndex(isset($styles[(int) ($cAttr['s'])]) ? (int) ($cAttr['s']) : 0); // issue 3495 if ($cell->getDataType() === DataType::TYPE_FORMULA) { $cell->getStyle()->setQuotePrefix(false); } } } ++$rowIndex; } ++$cIndex; } } if ($xmlSheetNS && $xmlSheetNS->ignoredErrors) { foreach ($xmlSheetNS->ignoredErrors->ignoredError as $ignoredErrorx) { $ignoredError = self::testSimpleXml($ignoredErrorx); $this->processIgnoredErrors($ignoredError, $docSheet); } } if (!$this->readDataOnly && $xmlSheetNS && $xmlSheetNS->sheetProtection) { $protAttr = $xmlSheetNS->sheetProtection->attributes() ?? []; foreach ($protAttr as $key => $value) { $method = 'set' . ucfirst($key); $docSheet->getProtection()->$method(self::boolean((string) $value)); } } if ($xmlSheet) { $this->readSheetProtection($docSheet, $xmlSheet); } if ($this->readDataOnly === false) { $this->readAutoFilter($xmlSheet, $docSheet); $this->readTables($xmlSheet, $docSheet, $dir, $fileWorksheet, $zip); } if ($xmlSheetNS && $xmlSheetNS->mergeCells && $xmlSheetNS->mergeCells->mergeCell && !$this->readDataOnly) { foreach ($xmlSheetNS->mergeCells->mergeCell as $mergeCellx) { /** @scrutinizer ignore-call */ $mergeCell = $mergeCellx->attributes(); $mergeRef = (string) ($mergeCell['ref'] ?? ''); if (strpos($mergeRef, ':') !== false) { $docSheet->mergeCells($mergeRef, Worksheet::MERGE_CELL_CONTENT_HIDE); } } } if ($xmlSheet && !$this->readDataOnly) { $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData); } if ($xmlSheet !== false && isset($xmlSheet->extLst, $xmlSheet->extLst->ext, $xmlSheet->extLst->ext['uri']) && ($xmlSheet->extLst->ext['uri'] == '{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}')) { // Create dataValidations node if does not exists, maybe is better inside the foreach ? if (!$xmlSheet->dataValidations) { $xmlSheet->addChild('dataValidations'); } foreach ($xmlSheet->extLst->ext->children(Namespaces::DATA_VALIDATIONS1)->dataValidations->dataValidation as $item) { $item = self::testSimpleXml($item); $node = self::testSimpleXml($xmlSheet->dataValidations)->addChild('dataValidation'); foreach ($item->attributes() ?? [] as $attr) { $node->addAttribute($attr->getName(), $attr); } $node->addAttribute('sqref', $item->children(Namespaces::DATA_VALIDATIONS2)->sqref); if (isset($item->formula1)) { $childNode = $node->addChild('formula1'); if ($childNode !== null) { // null should never happen $childNode[0] = (string) $item->formula1->children(Namespaces::DATA_VALIDATIONS2)->f; // @phpstan-ignore-line } } } } if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) { (new DataValidations($docSheet, $xmlSheet))->load(); } // unparsed sheet AlternateContent if ($xmlSheet && !$this->readDataOnly) { $mc = $xmlSheet->children(Namespaces::COMPATIBILITY); if ($mc->AlternateContent) { foreach ($mc->AlternateContent as $alternateContent) { $alternateContent = self::testSimpleXml($alternateContent); $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML(); } } } // Add hyperlinks if (!$this->readDataOnly) { $hyperlinkReader = new Hyperlinks($docSheet); // Locate hyperlink relations $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; if ($zip->locateName($relationsFileName)) { $relsWorksheet = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS); $hyperlinkReader->readHyperlinks($relsWorksheet); } // Loop through hyperlinks if ($xmlSheetNS && $xmlSheetNS->children($mainNS)->hyperlinks) { $hyperlinkReader->setHyperlinks($xmlSheetNS->children($mainNS)->hyperlinks); } } // Add comments $comments = []; $vmlComments = []; if (!$this->readDataOnly) { // Locate comment relations $commentRelations = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; if ($zip->locateName($commentRelations)) { $relsWorksheet = $this->loadZip($commentRelations, Namespaces::RELATIONSHIPS); foreach ($relsWorksheet->Relationship as $elex) { $ele = self::getAttributes($elex); if ($ele['Type'] == Namespaces::COMMENTS) { $comments[(string) $ele['Id']] = (string) $ele['Target']; } if ($ele['Type'] == Namespaces::VML) { $vmlComments[(string) $ele['Id']] = (string) $ele['Target']; } } } // Loop through comments foreach ($comments as $relName => $relPath) { // Load comments file $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); // okay to ignore namespace - using xpath $commentsFile = $this->loadZip($relPath, ''); // Utility variables $authors = []; $commentsFile->registerXpathNamespace('com', $mainNS); $authorPath = self::xpathNoFalse($commentsFile, 'com:authors/com:author'); foreach ($authorPath as $author) { $authors[] = (string) $author; } // Loop through contents $contentPath = self::xpathNoFalse($commentsFile, 'com:commentList/com:comment'); foreach ($contentPath as $comment) { $commentx = $comment->attributes(); $commentModel = $docSheet->getComment((string) $commentx['ref']); if (isset($commentx['authorId'])) { $commentModel->setAuthor($authors[(int) $commentx['authorId']]); } $commentModel->setText($this->parseRichText($comment->children($mainNS)->text)); } } // later we will remove from it real vmlComments $unparsedVmlDrawings = $vmlComments; $vmlDrawingContents = []; // Loop through VML comments foreach ($vmlComments as $relName => $relPath) { // Load VML comments file $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); try { // no namespace okay - processed with Xpath $vmlCommentsFile = $this->loadZip($relPath, '', true); $vmlCommentsFile->registerXPathNamespace('v', Namespaces::URN_VML); } catch (Throwable $ex) { //Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData continue; } // Locate VML drawings image relations $drowingImages = []; $VMLDrawingsRelations = dirname($relPath) . '/_rels/' . basename($relPath) . '.rels'; $vmlDrawingContents[$relName] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $relPath)); if ($zip->locateName($VMLDrawingsRelations)) { $relsVMLDrawing = $this->loadZip($VMLDrawingsRelations, Namespaces::RELATIONSHIPS); foreach ($relsVMLDrawing->Relationship as $elex) { $ele = self::getAttributes($elex); if ($ele['Type'] == Namespaces::IMAGE) { $drowingImages[(string) $ele['Id']] = (string) $ele['Target']; } } } $shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape'); foreach ($shapes as $shape) { $shape->registerXPathNamespace('v', Namespaces::URN_VML); if (isset($shape['style'])) { $style = (string) $shape['style']; $fillColor = strtoupper(substr((string) $shape['fillcolor'], 1)); $column = null; $row = null; $fillImageRelId = null; $fillImageTitle = ''; $clientData = $shape->xpath('.//x:ClientData'); if (is_array($clientData) && !empty($clientData)) { $clientData = $clientData[0]; if (isset($clientData['ObjectType']) && (string) $clientData['ObjectType'] == 'Note') { $temp = $clientData->xpath('.//x:Row'); if (is_array($temp)) { $row = $temp[0]; } $temp = $clientData->xpath('.//x:Column'); if (is_array($temp)) { $column = $temp[0]; } } } $fillImageRelNode = $shape->xpath('.//v:fill/@o:relid'); if (is_array($fillImageRelNode) && !empty($fillImageRelNode)) { $fillImageRelNode = $fillImageRelNode[0]; if (isset($fillImageRelNode['relid'])) { $fillImageRelId = (string) $fillImageRelNode['relid']; } } $fillImageTitleNode = $shape->xpath('.//v:fill/@o:title'); if (is_array($fillImageTitleNode) && !empty($fillImageTitleNode)) { $fillImageTitleNode = $fillImageTitleNode[0]; if (isset($fillImageTitleNode['title'])) { $fillImageTitle = (string) $fillImageTitleNode['title']; } } if (($column !== null) && ($row !== null)) { // Set comment properties $comment = $docSheet->getComment([$column + 1, $row + 1]); $comment->getFillColor()->setRGB($fillColor); if (isset($drowingImages[$fillImageRelId])) { $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); $objDrawing->setName($fillImageTitle); $imagePath = str_replace('../', 'xl/', $drowingImages[$fillImageRelId]); $objDrawing->setPath( 'zip://' . File::realpath($filename) . '#' . $imagePath, true, $zip ); $comment->setBackgroundImage($objDrawing); } // Parse style $styleArray = explode(';', str_replace(' ', '', $style)); foreach ($styleArray as $stylePair) { $stylePair = explode(':', $stylePair); if ($stylePair[0] == 'margin-left') { $comment->setMarginLeft($stylePair[1]); } if ($stylePair[0] == 'margin-top') { $comment->setMarginTop($stylePair[1]); } if ($stylePair[0] == 'width') { $comment->setWidth($stylePair[1]); } if ($stylePair[0] == 'height') { $comment->setHeight($stylePair[1]); } if ($stylePair[0] == 'visibility') { $comment->setVisible($stylePair[1] == 'visible'); } } unset($unparsedVmlDrawings[$relName]); } } } } // unparsed vmlDrawing if ($unparsedVmlDrawings) { foreach ($unparsedVmlDrawings as $rId => $relPath) { $rId = substr($rId, 3); // rIdXXX $unparsedVmlDrawing = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings']; $unparsedVmlDrawing[$rId] = []; $unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath); $unparsedVmlDrawing[$rId]['relFilePath'] = $relPath; $unparsedVmlDrawing[$rId]['content'] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $unparsedVmlDrawing[$rId]['filePath'])); unset($unparsedVmlDrawing); } } // Header/footer images if ($xmlSheetNS && $xmlSheetNS->legacyDrawingHF) { $vmlHfRid = ''; $vmlHfRidAttr = $xmlSheetNS->legacyDrawingHF->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT); if ($vmlHfRidAttr !== null && isset($vmlHfRidAttr['id'])) { $vmlHfRid = (string) $vmlHfRidAttr['id'][0]; } if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { $relsWorksheet = $this->loadZipNoNamespace(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels', Namespaces::RELATIONSHIPS); $vmlRelationship = ''; foreach ($relsWorksheet->Relationship as $ele) { if ((string) $ele['Type'] == Namespaces::VML && (string) $ele['Id'] === $vmlHfRid) { $vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); break; } } if ($vmlRelationship != '') { // Fetch linked images $relsVML = $this->loadZipNoNamespace(dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels', Namespaces::RELATIONSHIPS); $drawings = []; if (isset($relsVML->Relationship)) { foreach ($relsVML->Relationship as $ele) { if ($ele['Type'] == Namespaces::IMAGE) { $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']); } } } // Fetch VML document $vmlDrawing = $this->loadZipNoNamespace($vmlRelationship, ''); $vmlDrawing->registerXPathNamespace('v', Namespaces::URN_VML); $hfImages = []; $shapes = self::xpathNoFalse($vmlDrawing, '//v:shape'); foreach ($shapes as $idx => $shape) { $shape->registerXPathNamespace('v', Namespaces::URN_VML); $imageData = $shape->xpath('//v:imagedata'); if (empty($imageData)) { continue; } $imageData = $imageData[$idx]; $imageData = self::getAttributes($imageData, Namespaces::URN_MSOFFICE); $style = self::toCSSArray((string) $shape['style']); if (array_key_exists((string) $imageData['relid'], $drawings)) { $shapeId = (string) $shape['id']; $hfImages[$shapeId] = new HeaderFooterDrawing(); if (isset($imageData['title'])) { $hfImages[$shapeId]->setName((string) $imageData['title']); } $hfImages[$shapeId]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false, $zip); $hfImages[$shapeId]->setResizeProportional(false); $hfImages[$shapeId]->setWidth($style['width']); $hfImages[$shapeId]->setHeight($style['height']); if (isset($style['margin-left'])) { $hfImages[$shapeId]->setOffsetX($style['margin-left']); } $hfImages[$shapeId]->setOffsetY($style['margin-top']); $hfImages[$shapeId]->setResizeProportional(true); } } $docSheet->getHeaderFooter()->setImages($hfImages); } } } } // TODO: Autoshapes from twoCellAnchors! $drawingFilename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; if (substr($drawingFilename, 0, 7) === 'xl//xl/') { $drawingFilename = substr($drawingFilename, 4); } if (substr($drawingFilename, 0, 8) === '/xl//xl/') { $drawingFilename = substr($drawingFilename, 5); } if ($zip->locateName($drawingFilename)) { $relsWorksheet = $this->loadZipNoNamespace($drawingFilename, Namespaces::RELATIONSHIPS); $drawings = []; foreach ($relsWorksheet->Relationship as $ele) { if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { $eleTarget = (string) $ele['Target']; if (substr($eleTarget, 0, 4) === '/xl/') { $drawings[(string) $ele['Id']] = substr($eleTarget, 1); } else { $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); } } } if ($xmlSheetNS->drawing && !$this->readDataOnly) { $unparsedDrawings = []; $fileDrawing = null; foreach ($xmlSheetNS->drawing as $drawing) { $drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id'); $fileDrawing = $drawings[$drawingRelId]; $drawingFilename = dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels'; $relsDrawing = $this->loadZipNoNamespace($drawingFilename, $xmlNamespaceBase); $images = []; $hyperlinks = []; if ($relsDrawing && $relsDrawing->Relationship) { foreach ($relsDrawing->Relationship as $ele) { $eleType = (string) $ele['Type']; if ($eleType === Namespaces::HYPERLINK) { $hyperlinks[(string) $ele['Id']] = (string) $ele['Target']; } if ($eleType === "$xmlNamespaceBase/image") { $eleTarget = (string) $ele['Target']; if (substr($eleTarget, 0, 4) === '/xl/') { $eleTarget = substr($eleTarget, 1); $images[(string) $ele['Id']] = $eleTarget; } else { $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $eleTarget); } } elseif ($eleType === "$xmlNamespaceBase/chart") { if ($this->includeCharts) { $eleTarget = (string) $ele['Target']; if (substr($eleTarget, 0, 4) === '/xl/') { $index = substr($eleTarget, 1); } else { $index = self::dirAdd($fileDrawing, $eleTarget); } $charts[$index] = [ 'id' => (string) $ele['Id'], 'sheet' => $docSheet->getTitle(), ]; } } } } $xmlDrawing = $this->loadZipNoNamespace($fileDrawing, ''); $xmlDrawingChildren = $xmlDrawing->children(Namespaces::SPREADSHEET_DRAWING); if ($xmlDrawingChildren->oneCellAnchor) { foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) { $oneCellAnchor = self::testSimpleXml($oneCellAnchor); if ($oneCellAnchor->pic->blipFill) { /** @var SimpleXMLElement $blip */ $blip = $oneCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip; /** @var SimpleXMLElement $xfrm */ $xfrm = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm; /** @var SimpleXMLElement $outerShdw */ $outerShdw = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); $objDrawing->setName((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'name')); $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'descr')); $embedImageKey = (string) self::getArrayItem( self::getAttributes($blip, $xmlNamespaceBase), 'embed' ); if (isset($images[$embedImageKey])) { $objDrawing->setPath( 'zip://' . File::realpath($filename) . '#' . $images[$embedImageKey], false, $zip ); } else { $linkImageKey = (string) self::getArrayItem( $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'link' ); if (isset($images[$linkImageKey])) { $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); $objDrawing->setPath($url, false); } if ($objDrawing->getPath() === '') { continue; } } $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); $objDrawing->setOffsetX((int) Drawing::EMUToPixels($oneCellAnchor->from->colOff)); $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff)); $objDrawing->setResizeProportional(false); $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx'))); $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy'))); if ($xfrm) { $objDrawing->setRotation((int) Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); } if ($outerShdw) { $shadow = $objDrawing->getShadow(); $shadow->setVisible(true); $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000); } $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks); $objDrawing->setWorksheet($docSheet); } elseif ($this->includeCharts && $oneCellAnchor->graphicFrame) { // Exported XLSX from Google Sheets positions charts with a oneCellAnchor $coordinates = Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1); $offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff); $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff); $width = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx')); $height = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy')); $graphic = $oneCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; /** @var SimpleXMLElement $chartRef */ $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ 'fromCoordinate' => $coordinates, 'fromOffsetX' => $offsetX, 'fromOffsetY' => $offsetY, 'width' => $width, 'height' => $height, 'worksheetTitle' => $docSheet->getTitle(), 'oneCellAnchor' => true, ]; } } } if ($xmlDrawingChildren->twoCellAnchor) { foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) { $twoCellAnchor = self::testSimpleXml($twoCellAnchor); if ($twoCellAnchor->pic->blipFill) { $blip = $twoCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip; $xfrm = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm; $outerShdw = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); /** @scrutinizer ignore-call */ $editAs = $twoCellAnchor->attributes(); if (isset($editAs, $editAs['editAs'])) { $objDrawing->setEditAs($editAs['editAs']); } $objDrawing->setName((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'name')); $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'descr')); $embedImageKey = (string) self::getArrayItem( self::getAttributes($blip, $xmlNamespaceBase), 'embed' ); if (isset($images[$embedImageKey])) { $objDrawing->setPath( 'zip://' . File::realpath($filename) . '#' . $images[$embedImageKey], false, $zip ); } else { $linkImageKey = (string) self::getArrayItem( $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'link' ); if (isset($images[$linkImageKey])) { $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); $objDrawing->setPath($url, false); } if ($objDrawing->getPath() === '') { continue; } } $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1)); $objDrawing->setOffsetX(Drawing::EMUToPixels($twoCellAnchor->from->colOff)); $objDrawing->setOffsetY(Drawing::EMUToPixels($twoCellAnchor->from->rowOff)); $objDrawing->setCoordinates2(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1)); $objDrawing->setOffsetX2(Drawing::EMUToPixels($twoCellAnchor->to->colOff)); $objDrawing->setOffsetY2(Drawing::EMUToPixels($twoCellAnchor->to->rowOff)); $objDrawing->setResizeProportional(false); if ($xfrm) { $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cx'))); $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cy'))); $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); } if ($outerShdw) { $shadow = $objDrawing->getShadow(); $shadow->setVisible(true); $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000); } $this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks); $objDrawing->setWorksheet($docSheet); } elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) { $fromCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1); $fromOffsetX = Drawing::EMUToPixels($twoCellAnchor->from->colOff); $fromOffsetY = Drawing::EMUToPixels($twoCellAnchor->from->rowOff); $toCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1); $toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff); $toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff); $graphic = $twoCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; /** @var SimpleXMLElement $chartRef */ $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ 'fromCoordinate' => $fromCoordinate, 'fromOffsetX' => $fromOffsetX, 'fromOffsetY' => $fromOffsetY, 'toCoordinate' => $toCoordinate, 'toOffsetX' => $toOffsetX, 'toOffsetY' => $toOffsetY, 'worksheetTitle' => $docSheet->getTitle(), ]; } } } if ($xmlDrawingChildren->absoluteAnchor) { foreach ($xmlDrawingChildren->absoluteAnchor as $absoluteAnchor) { if (($this->includeCharts) && ($absoluteAnchor->graphicFrame)) { $graphic = $absoluteAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; /** @var SimpleXMLElement $chartRef */ $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); $width = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cx')[0]); $height = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cy')[0]); $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ 'fromCoordinate' => 'A1', 'fromOffsetX' => 0, 'fromOffsetY' => 0, 'width' => $width, 'height' => $height, 'worksheetTitle' => $docSheet->getTitle(), ]; } } } if (empty($relsDrawing) && $xmlDrawing->count() == 0) { // Save Drawing without rels and children as unparsed $unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML(); } } // store original rId of drawing files $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = []; foreach ($relsWorksheet->Relationship as $ele) { if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { $drawingRelId = (string) $ele['Id']; $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId; if (isset($unparsedDrawings[$drawingRelId])) { $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['Drawings'][$drawingRelId] = $unparsedDrawings[$drawingRelId]; } } } if ($xmlSheet->legacyDrawing && !$this->readDataOnly) { foreach ($xmlSheet->legacyDrawing as $drawing) { $drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id'); if (isset($vmlDrawingContents[$drawingRelId])) { $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['legacyDrawing'] = $vmlDrawingContents[$drawingRelId]; } } } // unparsed drawing AlternateContent $xmlAltDrawing = $this->loadZip((string) $fileDrawing, Namespaces::COMPATIBILITY); if ($xmlAltDrawing->AlternateContent) { foreach ($xmlAltDrawing->AlternateContent as $alternateContent) { $alternateContent = self::testSimpleXml($alternateContent); $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML(); } } } } $this->readFormControlProperties($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); $this->readPrinterSettings($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); // Loop through definedNames if ($xmlWorkbook->definedNames) { foreach ($xmlWorkbook->definedNames->definedName as $definedName) { // Extract range $extractedRange = (string) $definedName; if (($spos = strpos($extractedRange, '!')) !== false) { $extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos)); } else { $extractedRange = str_replace('$', '', $extractedRange); } // Valid range? if ($extractedRange == '') { continue; } // Some definedNames are only applicable if we are on the same sheet... if ((string) $definedName['localSheetId'] != '' && (string) $definedName['localSheetId'] == $oldSheetId) { // Switch on type switch ((string) $definedName['name']) { case '_xlnm._FilterDatabase': if ((string) $definedName['hidden'] !== '1') { $extractedRange = explode(',', $extractedRange); foreach ($extractedRange as $range) { $autoFilterRange = $range; if (strpos($autoFilterRange, ':') !== false) { $docSheet->getAutoFilter()->setRange($autoFilterRange); } } } break; case '_xlnm.Print_Titles': // Split $extractedRange $extractedRange = explode(',', $extractedRange); // Set print titles foreach ($extractedRange as $range) { $matches = []; $range = str_replace('$', '', $range); // check for repeating columns, e g. 'A:A' or 'A:D' if (preg_match('/!?([A-Z]+)\:([A-Z]+)$/', $range, $matches)) { $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$matches[1], $matches[2]]); } elseif (preg_match('/!?(\d+)\:(\d+)$/', $range, $matches)) { // check for repeating rows, e.g. '1:1' or '1:5' $docSheet->getPageSetup()->setRowsToRepeatAtTop([$matches[1], $matches[2]]); } } break; case '_xlnm.Print_Area': $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) ?: []; $newRangeSets = []; foreach ($rangeSets as $rangeSet) { [, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true); if (empty($rangeSet)) { continue; } if (strpos($rangeSet, ':') === false) { $rangeSet = $rangeSet . ':' . $rangeSet; } $newRangeSets[] = str_replace('$', '', $rangeSet); } if (count($newRangeSets) > 0) { $docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets)); } break; default: break; } } } } // Next sheet id ++$sheetId; } // Loop through definedNames if ($xmlWorkbook->definedNames) { foreach ($xmlWorkbook->definedNames->definedName as $definedName) { // Extract range $extractedRange = (string) $definedName; // Valid range? if ($extractedRange == '') { continue; } // Some definedNames are only applicable if we are on the same sheet... if ((string) $definedName['localSheetId'] != '') { // Local defined name // Switch on type switch ((string) $definedName['name']) { case '_xlnm._FilterDatabase': case '_xlnm.Print_Titles': case '_xlnm.Print_Area': break; default: if ($mapSheetId[(int) $definedName['localSheetId']] !== null) { $range = Worksheet::extractSheetTitle((string) $definedName, true); $scope = $excel->getSheet($mapSheetId[(int) $definedName['localSheetId']]); if (strpos((string) $definedName, '!') !== false) { $range[0] = str_replace("''", "'", $range[0]); $range[0] = str_replace("'", '', $range[0]); if ($worksheet = $excel->getSheetByName($range[0])) { // @phpstan-ignore-line $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $worksheet, $extractedRange, true, $scope)); } else { $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true, $scope)); } } else { $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true)); } } break; } } elseif (!isset($definedName['localSheetId'])) { $definedRange = (string) $definedName; // "Global" definedNames $locatedSheet = null; if (strpos((string) $definedName, '!') !== false) { // Modify range, and extract the first worksheet reference // Need to split on a comma or a space if not in quotes, and extract the first part. $definedNameValueParts = preg_split("/[ ,](?=([^']*'[^']*')*[^']*$)/miuU", $definedRange); // Extract sheet name [$extractedSheetName] = Worksheet::extractSheetTitle((string) $definedNameValueParts[0], true); // @phpstan-ignore-line $extractedSheetName = trim($extractedSheetName, "'"); // Locate sheet $locatedSheet = $excel->getSheetByName($extractedSheetName); } if ($locatedSheet === null && !DefinedName::testIfFormula($definedRange)) { $definedRange = '#REF!'; } $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $locatedSheet, $definedRange, false)); } } } } (new WorkbookView($excel))->viewSettings($xmlWorkbook, $mainNS, $mapSheetId, $this->readDataOnly); break; } } if (!$this->readDataOnly) { $contentTypes = $this->loadZip('[Content_Types].xml'); // Default content types foreach ($contentTypes->Default as $contentType) { switch ($contentType['ContentType']) { case 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings': $unparsedLoadedData['default_content_types'][(string) $contentType['Extension']] = (string) $contentType['ContentType']; break; } } // Override content types foreach ($contentTypes->Override as $contentType) { switch ($contentType['ContentType']) { case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml': if ($this->includeCharts) { $chartEntryRef = ltrim((string) $contentType['PartName'], '/'); $chartElements = $this->loadZip($chartEntryRef); $chartReader = new Chart($chartNS, $drawingNS); $objChart = $chartReader->readChart($chartElements, basename($chartEntryRef, '.xml')); if (isset($charts[$chartEntryRef])) { $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id']; if (isset($chartDetails[$chartPositionRef])) { $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart); // @phpstan-ignore-line $objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet'])); // For oneCellAnchor or absoluteAnchor positioned charts, // toCoordinate is not in the data. Does it need to be calculated? if (array_key_exists('toCoordinate', $chartDetails[$chartPositionRef])) { // twoCellAnchor $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']); } else { // oneCellAnchor or absoluteAnchor (e.g. Chart sheet) $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); $objChart->setBottomRightPosition('', $chartDetails[$chartPositionRef]['width'], $chartDetails[$chartPositionRef]['height']); if (array_key_exists('oneCellAnchor', $chartDetails[$chartPositionRef])) { $objChart->setOneCellAnchor($chartDetails[$chartPositionRef]['oneCellAnchor']); } } } } } break; // unparsed case 'application/vnd.ms-excel.controlproperties+xml': $unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType']; break; } } } $excel->setUnparsedLoadedData($unparsedLoadedData); $zip->close(); return $excel; } /** * @return RichText */ private function parseRichText(?SimpleXMLElement $is) { $value = new RichText(); if (isset($is->t)) { $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $is->t)); } elseif ($is !== null) { if (is_object($is->r)) { /** @var SimpleXMLElement $run */ foreach ($is->r as $run) { if (!isset($run->rPr)) { $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $run->t)); } else { $objText = $value->createTextRun(StringHelper::controlCharacterOOXML2PHP((string) $run->t)); $objFont = $objText->getFont() ?? new StyleFont(); if (isset($run->rPr->rFont)) { $attr = $run->rPr->rFont->attributes(); if (isset($attr['val'])) { $objFont->setName((string) $attr['val']); } } if (isset($run->rPr->sz)) { $attr = $run->rPr->sz->attributes(); if (isset($attr['val'])) { $objFont->setSize((float) $attr['val']); } } if (isset($run->rPr->color)) { $objFont->setColor(new Color($this->styleReader->readColor($run->rPr->color))); } if (isset($run->rPr->b)) { $attr = $run->rPr->b->attributes(); if ( (isset($attr['val']) && self::boolean((string) $attr['val'])) || (!isset($attr['val'])) ) { $objFont->setBold(true); } } if (isset($run->rPr->i)) { $attr = $run->rPr->i->attributes(); if ( (isset($attr['val']) && self::boolean((string) $attr['val'])) || (!isset($attr['val'])) ) { $objFont->setItalic(true); } } if (isset($run->rPr->vertAlign)) { $attr = $run->rPr->vertAlign->attributes(); if (isset($attr['val'])) { $vertAlign = strtolower((string) $attr['val']); if ($vertAlign == 'superscript') { $objFont->setSuperscript(true); } if ($vertAlign == 'subscript') { $objFont->setSubscript(true); } } } if (isset($run->rPr->u)) { $attr = $run->rPr->u->attributes(); if (!isset($attr['val'])) { $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE); } else { $objFont->setUnderline((string) $attr['val']); } } if (isset($run->rPr->strike)) { $attr = $run->rPr->strike->attributes(); if ( (isset($attr['val']) && self::boolean((string) $attr['val'])) || (!isset($attr['val'])) ) { $objFont->setStrikethrough(true); } } } } } } return $value; } private function readRibbon(Spreadsheet $excel, string $customUITarget, ZipArchive $zip): void { $baseDir = dirname($customUITarget); $nameCustomUI = basename($customUITarget); // get the xml file (ribbon) $localRibbon = $this->getFromZipArchive($zip, $customUITarget); $customUIImagesNames = []; $customUIImagesBinaries = []; // something like customUI/_rels/customUI.xml.rels $pathRels = $baseDir . '/_rels/' . $nameCustomUI . '.rels'; $dataRels = $this->getFromZipArchive($zip, $pathRels); if ($dataRels) { // exists and not empty if the ribbon have some pictures (other than internal MSO) $UIRels = simplexml_load_string( $this->getSecurityScannerOrThrow() ->scan($dataRels) ); if (false !== $UIRels) { // we need to save id and target to avoid parsing customUI.xml and "guess" if it's a pseudo callback who load the image foreach ($UIRels->Relationship as $ele) { if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/image') { // an image ? $customUIImagesNames[(string) $ele['Id']] = (string) $ele['Target']; $customUIImagesBinaries[(string) $ele['Target']] = $this->getFromZipArchive($zip, $baseDir . '/' . (string) $ele['Target']); } } } } if ($localRibbon) { $excel->setRibbonXMLData($customUITarget, $localRibbon); if (count($customUIImagesNames) > 0 && count($customUIImagesBinaries) > 0) { $excel->setRibbonBinObjects($customUIImagesNames, $customUIImagesBinaries); } else { $excel->setRibbonBinObjects(null, null); } } else { $excel->setRibbonXMLData(null, null); $excel->setRibbonBinObjects(null, null); } } /** * @param null|array|bool|SimpleXMLElement $array * @param int|string $key * * @return mixed */ private static function getArrayItem($array, $key = 0) { return ($array === null || is_bool($array)) ? null : ($array[$key] ?? null); } /** * @param null|SimpleXMLElement|string $base * @param null|SimpleXMLElement|string $add */ private static function dirAdd($base, $add): string { $base = (string) $base; $add = (string) $add; return (string) preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add"); } private static function toCSSArray(string $style): array { $style = self::stripWhiteSpaceFromStyleString($style); $temp = explode(';', $style); $style = []; foreach ($temp as $item) { $item = explode(':', $item); if (strpos($item[1], 'px') !== false) { $item[1] = str_replace('px', '', $item[1]); } if (strpos($item[1], 'pt') !== false) { $item[1] = str_replace('pt', '', $item[1]); $item[1] = (string) Font::fontSizeToPixels((int) $item[1]); } if (strpos($item[1], 'in') !== false) { $item[1] = str_replace('in', '', $item[1]); $item[1] = (string) Font::inchSizeToPixels((int) $item[1]); } if (strpos($item[1], 'cm') !== false) { $item[1] = str_replace('cm', '', $item[1]); $item[1] = (string) Font::centimeterSizeToPixels((int) $item[1]); } $style[$item[0]] = $item[1]; } return $style; } public static function stripWhiteSpaceFromStyleString(string $string): string { return trim(str_replace(["\r", "\n", ' '], '', $string), ';'); } private static function boolean(string $value): bool { if (is_numeric($value)) { return (bool) $value; } return $value === 'true' || $value === 'TRUE'; } /** * @param array $hyperlinks */ private function readHyperLinkDrawing(\PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing, SimpleXMLElement $cellAnchor, $hyperlinks): void { $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; if ($hlinkClick->count() === 0) { return; } $hlinkId = (string) self::getAttributes($hlinkClick, Namespaces::SCHEMA_OFFICE_DOCUMENT)['id']; $hyperlink = new Hyperlink( $hyperlinks[$hlinkId], (string) self::getArrayItem(self::getAttributes($cellAnchor->pic->nvPicPr->cNvPr), 'name') ); $objDrawing->setHyperlink($hyperlink); } private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook): void { if (!$xmlWorkbook->workbookProtection) { return; } $excel->getSecurity()->setLockRevision(self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision')); $excel->getSecurity()->setLockStructure(self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure')); $excel->getSecurity()->setLockWindows(self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows')); if ($xmlWorkbook->workbookProtection['revisionsPassword']) { $excel->getSecurity()->setRevisionsPassword( (string) $xmlWorkbook->workbookProtection['revisionsPassword'], true ); } if ($xmlWorkbook->workbookProtection['workbookPassword']) { $excel->getSecurity()->setWorkbookPassword( (string) $xmlWorkbook->workbookProtection['workbookPassword'], true ); } } private static function getLockValue(SimpleXmlElement $protection, string $key): ?bool { $returnValue = null; $protectKey = $protection[$key]; if (!empty($protectKey)) { $protectKey = (string) $protectKey; $returnValue = $protectKey !== 'false' && (bool) $protectKey; } return $returnValue; } private function readFormControlProperties(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void { $zip = $this->zip; if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { return; } $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS); $ctrlProps = []; foreach ($relsWorksheet->Relationship as $ele) { if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/ctrlProp') { $ctrlProps[(string) $ele['Id']] = $ele; } } $unparsedCtrlProps = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['ctrlProps']; foreach ($ctrlProps as $rId => $ctrlProp) { $rId = substr($rId, 3); // rIdXXX $unparsedCtrlProps[$rId] = []; $unparsedCtrlProps[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $ctrlProp['Target']); $unparsedCtrlProps[$rId]['relFilePath'] = (string) $ctrlProp['Target']; $unparsedCtrlProps[$rId]['content'] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $unparsedCtrlProps[$rId]['filePath'])); } unset($unparsedCtrlProps); } private function readPrinterSettings(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void { $zip = $this->zip; if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { return; } $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS); $sheetPrinterSettings = []; foreach ($relsWorksheet->Relationship as $ele) { if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/printerSettings') { $sheetPrinterSettings[(string) $ele['Id']] = $ele; } } $unparsedPrinterSettings = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings']; foreach ($sheetPrinterSettings as $rId => $printerSettings) { $rId = substr($rId, 3); // rIdXXX if (substr($rId, -2) !== 'ps') { $rId = $rId . 'ps'; // rIdXXX, add 'ps' suffix to avoid identical resource identifier collision with unparsed vmlDrawing } $unparsedPrinterSettings[$rId] = []; $unparsedPrinterSettings[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $printerSettings['Target']); $unparsedPrinterSettings[$rId]['relFilePath'] = (string) $printerSettings['Target']; $unparsedPrinterSettings[$rId]['content'] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $unparsedPrinterSettings[$rId]['filePath'])); } unset($unparsedPrinterSettings); } private function getWorkbookBaseName(): array { $workbookBasename = ''; $xmlNamespaceBase = ''; // check if it is an OOXML archive $rels = $this->loadZip(self::INITIAL_FILE); foreach ($rels->children(Namespaces::RELATIONSHIPS)->Relationship as $rel) { $rel = self::getAttributes($rel); $type = (string) $rel['Type']; switch ($type) { case Namespaces::OFFICE_DOCUMENT: case Namespaces::PURL_OFFICE_DOCUMENT: $basename = basename((string) $rel['Target']); $xmlNamespaceBase = dirname($type); if (preg_match('/workbook.*\.xml/', $basename)) { $workbookBasename = $basename; } break; } } return [$workbookBasename, $xmlNamespaceBase]; } private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void { if ($this->readDataOnly || !$xmlSheet->sheetProtection) { return; } $algorithmName = (string) $xmlSheet->sheetProtection['algorithmName']; $protection = $docSheet->getProtection(); $protection->setAlgorithm($algorithmName); if ($algorithmName) { $protection->setPassword((string) $xmlSheet->sheetProtection['hashValue'], true); $protection->setSalt((string) $xmlSheet->sheetProtection['saltValue']); $protection->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']); } else { $protection->setPassword((string) $xmlSheet->sheetProtection['password'], true); } if ($xmlSheet->protectedRanges->protectedRange) { foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) { $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true); } } } private function readAutoFilter( SimpleXMLElement $xmlSheet, Worksheet $docSheet ): void { if ($xmlSheet && $xmlSheet->autoFilter) { (new AutoFilter($docSheet, $xmlSheet))->load(); } } private function readTables( SimpleXMLElement $xmlSheet, Worksheet $docSheet, string $dir, string $fileWorksheet, ZipArchive $zip ): void { if ($xmlSheet && $xmlSheet->tableParts && (int) $xmlSheet->tableParts['count'] > 0) { $this->readTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet); } } private function readTablesInTablesFile( SimpleXMLElement $xmlSheet, string $dir, string $fileWorksheet, ZipArchive $zip, Worksheet $docSheet ): void { foreach ($xmlSheet->tableParts->tablePart as $tablePart) { $relation = self::getAttributes($tablePart, Namespaces::SCHEMA_OFFICE_DOCUMENT); $tablePartRel = (string) $relation['id']; $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; if ($zip->locateName($relationsFileName)) { $relsTableReferences = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS); foreach ($relsTableReferences->Relationship as $relationship) { $relationshipAttributes = self::getAttributes($relationship, ''); if ((string) $relationshipAttributes['Id'] === $tablePartRel) { $relationshipFileName = (string) $relationshipAttributes['Target']; $relationshipFilePath = dirname("$dir/$fileWorksheet") . '/' . $relationshipFileName; $relationshipFilePath = File::realpath($relationshipFilePath); if ($this->fileExistsInArchive($this->zip, $relationshipFilePath)) { $tableXml = $this->loadZip($relationshipFilePath); (new TableReader($docSheet, $tableXml))->load(); } } } } } } private static function extractStyles(?SimpleXMLElement $sxml, string $node1, string $node2): array { $array = []; if ($sxml && $sxml->{$node1}->{$node2}) { foreach ($sxml->{$node1}->{$node2} as $node) { $array[] = $node; } } return $array; } private static function extractPalette(?SimpleXMLElement $sxml): array { $array = []; if ($sxml && $sxml->colors->indexedColors) { foreach ($sxml->colors->indexedColors->rgbColor as $node) { if ($node !== null) { $attr = $node->attributes(); if (isset($attr['rgb'])) { $array[] = (string) $attr['rgb']; } } } } return $array; } private function processIgnoredErrors(SimpleXMLElement $xml, Worksheet $sheet): void { $attributes = self::getAttributes($xml); $sqref = (string) ($attributes['sqref'] ?? ''); $numberStoredAsText = (string) ($attributes['numberStoredAsText'] ?? ''); $formula = (string) ($attributes['formula'] ?? ''); $twoDigitTextYear = (string) ($attributes['twoDigitTextYear'] ?? ''); $evalError = (string) ($attributes['evalError'] ?? ''); if (!empty($sqref)) { $explodedSqref = explode(' ', $sqref); $pattern1 = '/^([A-Z]{1,3})([0-9]{1,7})(:([A-Z]{1,3})([0-9]{1,7}))?$/'; foreach ($explodedSqref as $sqref1) { if (preg_match($pattern1, $sqref1, $matches) === 1) { $firstRow = $matches[2]; $firstCol = $matches[1]; if (array_key_exists(3, $matches)) { $lastCol = $matches[4]; $lastRow = $matches[5]; } else { $lastCol = $firstCol; $lastRow = $firstRow; } ++$lastCol; for ($row = $firstRow; $row <= $lastRow; ++$row) { for ($col = $firstCol; $col !== $lastCol; ++$col) { if ($numberStoredAsText === '1') { $sheet->getCell("$col$row")->getIgnoredErrors()->setNumberStoredAsText(true); } if ($formula === '1') { $sheet->getCell("$col$row")->getIgnoredErrors()->setFormula(true); } if ($twoDigitTextYear === '1') { $sheet->getCell("$col$row")->getIgnoredErrors()->setTwoDigitTextYear(true); } if ($evalError === '1') { $sheet->getCell("$col$row")->getIgnoredErrors()->setEvalError(true); } } } } } } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php000064400000047211152427537250015756 0ustar00openFile($filename); } catch (ReaderException $e) { return false; } // Read sample data (first 2 KB will do) $data = (string) fread($this->fileHandle, 2048); // Count delimiters in file $delimiterCount = substr_count($data, ';'); $hasDelimiter = $delimiterCount > 0; // Analyze first line looking for ID; signature $lines = explode("\n", $data); $hasId = substr($lines[0], 0, 4) === 'ID;P'; fclose($this->fileHandle); return $hasDelimiter && $hasId; } private function canReadOrBust(string $filename): void { if (!$this->canRead($filename)) { throw new ReaderException($filename . ' is an Invalid SYLK file.'); } $this->openFile($filename); } /** * Set input encoding. * * @deprecated no use is made of this property * * @param string $inputEncoding Input encoding, eg: 'ANSI' * * @return $this * * @codeCoverageIgnore */ public function setInputEncoding($inputEncoding) { $this->inputEncoding = $inputEncoding; return $this; } /** * Get input encoding. * * @deprecated no use is made of this property * * @return string * * @codeCoverageIgnore */ public function getInputEncoding() { return $this->inputEncoding; } /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * * @param string $filename * * @return array */ public function listWorksheetInfo($filename) { // Open file $this->canReadOrBust($filename); $fileHandle = $this->fileHandle; rewind($fileHandle); $worksheetInfo = []; $worksheetInfo[0]['worksheetName'] = basename($filename, '.slk'); // loop through one row (line) at a time in the file $rowIndex = 0; $columnIndex = 0; while (($rowData = fgets($fileHandle)) !== false) { $columnIndex = 0; // convert SYLK encoded $rowData to UTF-8 $rowData = StringHelper::SYLKtoUTF8($rowData); // explode each row at semicolons while taking into account that literal semicolon (;) // is escaped like this (;;) $rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowData))))); $dataType = array_shift($rowData); if ($dataType == 'B') { foreach ($rowData as $rowDatum) { switch ($rowDatum[0]) { case 'X': $columnIndex = (int) substr($rowDatum, 1) - 1; break; case 'Y': $rowIndex = substr($rowDatum, 1); break; } } break; } } $worksheetInfo[0]['lastColumnIndex'] = $columnIndex; $worksheetInfo[0]['totalRows'] = $rowIndex; $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1); $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1; // Close file fclose($fileHandle); return $worksheetInfo; } /** * Loads PhpSpreadsheet from file. */ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); // Load into this instance return $this->loadIntoExisting($filename, $spreadsheet); } private const COLOR_ARRAY = [ 'FF00FFFF', // 0 - cyan 'FF000000', // 1 - black 'FFFFFFFF', // 2 - white 'FFFF0000', // 3 - red 'FF00FF00', // 4 - green 'FF0000FF', // 5 - blue 'FFFFFF00', // 6 - yellow 'FFFF00FF', // 7 - magenta ]; private const FONT_STYLE_MAPPINGS = [ 'B' => 'bold', 'I' => 'italic', 'U' => 'underline', ]; private function processFormula(string $rowDatum, bool &$hasCalculatedValue, string &$cellDataFormula, string $row, string $column): void { $cellDataFormula = '=' . substr($rowDatum, 1); // Convert R1C1 style references to A1 style references (but only when not quoted) $temp = explode('"', $cellDataFormula); $key = false; foreach ($temp as &$value) { // Only count/replace in alternate array entries $key = $key === false; if ($key) { preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); // Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way // through the formula from left to right. Reversing means that we work right to left.through // the formula $cellReferences = array_reverse($cellReferences); // Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent, // then modify the formula to use that new reference foreach ($cellReferences as $cellReference) { $rowReference = $cellReference[2][0]; // Empty R reference is the current row if ($rowReference == '') { $rowReference = $row; } // Bracketed R references are relative to the current row if ($rowReference[0] == '[') { $rowReference = (int) $row + (int) trim($rowReference, '[]'); } $columnReference = $cellReference[4][0]; // Empty C reference is the current column if ($columnReference == '') { $columnReference = $column; } // Bracketed C references are relative to the current column if ($columnReference[0] == '[') { $columnReference = (int) $column + (int) trim($columnReference, '[]'); } $A1CellReference = Coordinate::stringFromColumnIndex((int) $columnReference) . $rowReference; $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0])); } } } unset($value); // Then rebuild the formula string $cellDataFormula = implode('"', $temp); $hasCalculatedValue = true; } private function processCRecord(array $rowData, Spreadsheet &$spreadsheet, string &$row, string &$column): void { // Read cell value data $hasCalculatedValue = false; $cellDataFormula = $cellData = ''; foreach ($rowData as $rowDatum) { switch ($rowDatum[0]) { case 'C': case 'X': $column = substr($rowDatum, 1); break; case 'R': case 'Y': $row = substr($rowDatum, 1); break; case 'K': $cellData = substr($rowDatum, 1); break; case 'E': $this->processFormula($rowDatum, $hasCalculatedValue, $cellDataFormula, $row, $column); break; case 'A': $comment = substr($rowDatum, 1); $columnLetter = Coordinate::stringFromColumnIndex((int) $column); $spreadsheet->getActiveSheet() ->getComment("$columnLetter$row") ->getText() ->createText($comment); break; } } $columnLetter = Coordinate::stringFromColumnIndex((int) $column); $cellData = Calculation::unwrapResult($cellData); // Set cell value $this->processCFinal($spreadsheet, $hasCalculatedValue, $cellDataFormula, $cellData, "$columnLetter$row"); } private function processCFinal(Spreadsheet &$spreadsheet, bool $hasCalculatedValue, string $cellDataFormula, string $cellData, string $coordinate): void { // Set cell value $spreadsheet->getActiveSheet()->getCell($coordinate)->setValue(($hasCalculatedValue) ? $cellDataFormula : $cellData); if ($hasCalculatedValue) { $cellData = Calculation::unwrapResult($cellData); $spreadsheet->getActiveSheet()->getCell($coordinate)->setCalculatedValue($cellData); } } private function processFRecord(array $rowData, Spreadsheet &$spreadsheet, string &$row, string &$column): void { // Read cell formatting $formatStyle = $columnWidth = ''; $startCol = $endCol = ''; $fontStyle = ''; $styleData = []; foreach ($rowData as $rowDatum) { switch ($rowDatum[0]) { case 'C': case 'X': $column = substr($rowDatum, 1); break; case 'R': case 'Y': $row = substr($rowDatum, 1); break; case 'P': $formatStyle = $rowDatum; break; case 'W': [$startCol, $endCol, $columnWidth] = explode(' ', substr($rowDatum, 1)); break; case 'S': $this->styleSettings($rowDatum, $styleData, $fontStyle); break; } } $this->addFormats($spreadsheet, $formatStyle, $row, $column); $this->addFonts($spreadsheet, $fontStyle, $row, $column); $this->addStyle($spreadsheet, $styleData, $row, $column); $this->addWidth($spreadsheet, $columnWidth, $startCol, $endCol); } private const STYLE_SETTINGS_FONT = ['D' => 'bold', 'I' => 'italic']; private const STYLE_SETTINGS_BORDER = [ 'B' => 'bottom', 'L' => 'left', 'R' => 'right', 'T' => 'top', ]; private function styleSettings(string $rowDatum, array &$styleData, string &$fontStyle): void { $styleSettings = substr($rowDatum, 1); $iMax = strlen($styleSettings); for ($i = 0; $i < $iMax; ++$i) { $char = $styleSettings[$i]; if (array_key_exists($char, self::STYLE_SETTINGS_FONT)) { $styleData['font'][self::STYLE_SETTINGS_FONT[$char]] = true; } elseif (array_key_exists($char, self::STYLE_SETTINGS_BORDER)) { $styleData['borders'][self::STYLE_SETTINGS_BORDER[$char]]['borderStyle'] = Border::BORDER_THIN; } elseif ($char == 'S') { $styleData['fill']['fillType'] = \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_PATTERN_GRAY125; } elseif ($char == 'M') { if (preg_match('/M([1-9]\\d*)/', $styleSettings, $matches)) { $fontStyle = $matches[1]; } } } } private function addFormats(Spreadsheet &$spreadsheet, string $formatStyle, string $row, string $column): void { if ($formatStyle && $column > '' && $row > '') { $columnLetter = Coordinate::stringFromColumnIndex((int) $column); if (isset($this->formats[$formatStyle])) { $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->formats[$formatStyle]); } } } private function addFonts(Spreadsheet &$spreadsheet, string $fontStyle, string $row, string $column): void { if ($fontStyle && $column > '' && $row > '') { $columnLetter = Coordinate::stringFromColumnIndex((int) $column); if (isset($this->fonts[$fontStyle])) { $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->fonts[$fontStyle]); } } } private function addStyle(Spreadsheet &$spreadsheet, array $styleData, string $row, string $column): void { if ((!empty($styleData)) && $column > '' && $row > '') { $columnLetter = Coordinate::stringFromColumnIndex((int) $column); $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($styleData); } } private function addWidth(Spreadsheet $spreadsheet, string $columnWidth, string $startCol, string $endCol): void { if ($columnWidth > '') { if ($startCol == $endCol) { $startCol = Coordinate::stringFromColumnIndex((int) $startCol); $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth); } else { $startCol = Coordinate::stringFromColumnIndex((int) $startCol); $endCol = Coordinate::stringFromColumnIndex((int) $endCol); $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth); do { $spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth((float) $columnWidth); } while ($startCol !== $endCol); } } } private function processPRecord(array $rowData, Spreadsheet &$spreadsheet): void { // Read shared styles $formatArray = []; $fromFormats = ['\-', '\ ']; $toFormats = ['-', ' ']; foreach ($rowData as $rowDatum) { switch ($rowDatum[0]) { case 'P': $formatArray['numberFormat']['formatCode'] = str_replace($fromFormats, $toFormats, substr($rowDatum, 1)); break; case 'E': case 'F': $formatArray['font']['name'] = substr($rowDatum, 1); break; case 'M': $formatArray['font']['size'] = ((float) substr($rowDatum, 1)) / 20; break; case 'L': $this->processPColors($rowDatum, $formatArray); break; case 'S': $this->processPFontStyles($rowDatum, $formatArray); break; } } $this->processPFinal($spreadsheet, $formatArray); } private function processPColors(string $rowDatum, array &$formatArray): void { if (preg_match('/L([1-9]\\d*)/', $rowDatum, $matches)) { $fontColor = $matches[1] % 8; $formatArray['font']['color']['argb'] = self::COLOR_ARRAY[$fontColor]; } } private function processPFontStyles(string $rowDatum, array &$formatArray): void { $styleSettings = substr($rowDatum, 1); $iMax = strlen($styleSettings); for ($i = 0; $i < $iMax; ++$i) { if (array_key_exists($styleSettings[$i], self::FONT_STYLE_MAPPINGS)) { $formatArray['font'][self::FONT_STYLE_MAPPINGS[$styleSettings[$i]]] = true; } } } private function processPFinal(Spreadsheet &$spreadsheet, array $formatArray): void { if (array_key_exists('numberFormat', $formatArray)) { $this->formats['P' . $this->format] = $formatArray; ++$this->format; } elseif (array_key_exists('font', $formatArray)) { ++$this->fontcount; $this->fonts[$this->fontcount] = $formatArray; if ($this->fontcount === 1) { $spreadsheet->getDefaultStyle()->applyFromArray($formatArray); } } } /** * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. * * @param string $filename * * @return Spreadsheet */ public function loadIntoExisting($filename, Spreadsheet $spreadsheet) { // Open file $this->canReadOrBust($filename); $fileHandle = $this->fileHandle; rewind($fileHandle); // Create new Worksheets while ($spreadsheet->getSheetCount() <= $this->sheetIndex) { $spreadsheet->createSheet(); } $spreadsheet->setActiveSheetIndex($this->sheetIndex); $spreadsheet->getActiveSheet()->setTitle(substr(basename($filename, '.slk'), 0, Worksheet::SHEET_TITLE_MAXIMUM_LENGTH)); // Loop through file $column = $row = ''; // loop through one row (line) at a time in the file while (($rowDataTxt = fgets($fileHandle)) !== false) { // convert SYLK encoded $rowData to UTF-8 $rowDataTxt = StringHelper::SYLKtoUTF8($rowDataTxt); // explode each row at semicolons while taking into account that literal semicolon (;) // is escaped like this (;;) $rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowDataTxt))))); $dataType = array_shift($rowData); if ($dataType == 'P') { // Read shared styles $this->processPRecord($rowData, $spreadsheet); } elseif ($dataType == 'C') { // Read cell value data $this->processCRecord($rowData, $spreadsheet, $row, $column); } elseif ($dataType == 'F') { // Read cell formatting $this->processFRecord($rowData, $spreadsheet, $row, $column); } else { $this->columnRowFromRowData($rowData, $column, $row); } } // Close file fclose($fileHandle); // Return return $spreadsheet; } private function columnRowFromRowData(array $rowData, string &$column, string &$row): void { foreach ($rowData as $rowDatum) { $char0 = $rowDatum[0]; if ($char0 === 'X' || $char0 == 'C') { $column = substr($rowDatum, 1); } elseif ($char0 === 'Y' || $char0 == 'R') { $row = substr($rowDatum, 1); } } } /** * Get sheet index. * * @return int */ public function getSheetIndex() { return $this->sheetIndex; } /** * Set sheet index. * * @param int $sheetIndex Sheet index * * @return $this */ public function setSheetIndex($sheetIndex) { $this->sheetIndex = $sheetIndex; return $this; } } phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php000064400000053161152427537250016777 0ustar00 [ '10' => DataType::TYPE_NULL, '20' => DataType::TYPE_BOOL, '30' => DataType::TYPE_NUMERIC, // Integer doesn't exist in Excel '40' => DataType::TYPE_NUMERIC, // Float '50' => DataType::TYPE_ERROR, '60' => DataType::TYPE_STRING, //'70': // Cell Range //'80': // Array ], ]; /** * Create a new Gnumeric. */ public function __construct() { parent::__construct(); $this->referenceHelper = ReferenceHelper::getInstance(); $this->securityScanner = XmlScanner::getInstance($this); } /** * Can the current IReader read the file? */ public function canRead(string $filename): bool { $data = null; if (File::testFileNoThrow($filename)) { $data = $this->gzfileGetContents($filename); if (strpos($data, self::NAMESPACE_GNM) === false) { $data = ''; } } return !empty($data); } private static function matchXml(XMLReader $xml, string $expectedLocalName): bool { return $xml->namespaceURI === self::NAMESPACE_GNM && $xml->localName === $expectedLocalName && $xml->nodeType === XMLReader::ELEMENT; } /** * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. * * @param string $filename * * @return array */ public function listWorksheetNames($filename) { File::assertFile($filename); if (!$this->canRead($filename)) { throw new Exception($filename . ' is an invalid Gnumeric file.'); } $xml = new XMLReader(); $contents = $this->gzfileGetContents($filename); $xml->xml($contents); $xml->setParserProperty(2, true); $worksheetNames = []; while ($xml->read()) { if (self::matchXml($xml, 'SheetName')) { $xml->read(); // Move onto the value node $worksheetNames[] = (string) $xml->value; } elseif (self::matchXml($xml, 'Sheets')) { // break out of the loop once we've got our sheet names rather than parse the entire file break; } } return $worksheetNames; } /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * * @param string $filename * * @return array */ public function listWorksheetInfo($filename) { File::assertFile($filename); if (!$this->canRead($filename)) { throw new Exception($filename . ' is an invalid Gnumeric file.'); } $xml = new XMLReader(); $contents = $this->gzfileGetContents($filename); $xml->xml($contents); $xml->setParserProperty(2, true); $worksheetInfo = []; while ($xml->read()) { if (self::matchXml($xml, 'Sheet')) { $tmpInfo = [ 'worksheetName' => '', 'lastColumnLetter' => 'A', 'lastColumnIndex' => 0, 'totalRows' => 0, 'totalColumns' => 0, ]; while ($xml->read()) { if (self::matchXml($xml, 'Name')) { $xml->read(); // Move onto the value node $tmpInfo['worksheetName'] = (string) $xml->value; } elseif (self::matchXml($xml, 'MaxCol')) { $xml->read(); // Move onto the value node $tmpInfo['lastColumnIndex'] = (int) $xml->value; $tmpInfo['totalColumns'] = (int) $xml->value + 1; } elseif (self::matchXml($xml, 'MaxRow')) { $xml->read(); // Move onto the value node $tmpInfo['totalRows'] = (int) $xml->value + 1; break; } } $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); $worksheetInfo[] = $tmpInfo; } } return $worksheetInfo; } /** * @param string $filename * * @return string */ private function gzfileGetContents($filename) { $data = ''; $contents = @file_get_contents($filename); if ($contents !== false) { if (substr($contents, 0, 2) === "\x1f\x8b") { // Check if gzlib functions are available if (function_exists('gzdecode')) { $contents = @gzdecode($contents); if ($contents !== false) { $data = $contents; } } } else { $data = $contents; } } if ($data !== '') { $data = $this->getSecurityScannerOrThrow()->scan($data); } return $data; } public static function gnumericMappings(): array { return array_merge(self::$mappings, Styles::$mappings); } private function processComments(SimpleXMLElement $sheet): void { if ((!$this->readDataOnly) && (isset($sheet->Objects))) { foreach ($sheet->Objects->children(self::NAMESPACE_GNM) as $key => $comment) { $commentAttributes = $comment->attributes(); // Only comment objects are handled at the moment if ($commentAttributes && $commentAttributes->Text) { $this->spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound) ->setAuthor((string) $commentAttributes->Author) ->setText($this->parseRichText((string) $commentAttributes->Text)); } } } } /** * @param mixed $value */ private static function testSimpleXml($value): SimpleXMLElement { return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement(''); } /** * Loads Spreadsheet from file. */ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); $spreadsheet->removeSheetByIndex(0); // Load into this instance return $this->loadIntoExisting($filename, $spreadsheet); } /** * Loads from file into Spreadsheet instance. */ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet { $this->spreadsheet = $spreadsheet; File::assertFile($filename); if (!$this->canRead($filename)) { throw new Exception($filename . ' is an invalid Gnumeric file.'); } $gFileData = $this->gzfileGetContents($filename); $xml2 = simplexml_load_string($gFileData); $xml = self::testSimpleXml($xml2); $gnmXML = $xml->children(self::NAMESPACE_GNM); (new Properties($this->spreadsheet))->readProperties($xml, $gnmXML); $worksheetID = 0; foreach ($gnmXML->Sheets->Sheet as $sheetOrNull) { $sheet = self::testSimpleXml($sheetOrNull); $worksheetName = (string) $sheet->Name; if (is_array($this->loadSheetsOnly) && !in_array($worksheetName, $this->loadSheetsOnly, true)) { continue; } $maxRow = $maxCol = 0; // Create new Worksheet $this->spreadsheet->createSheet(); $this->spreadsheet->setActiveSheetIndex($worksheetID); // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet // name in line with the formula, not the reverse $this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false); $visibility = $sheet->attributes()['Visibility'] ?? 'GNM_SHEET_VISIBILITY_VISIBLE'; if ((string) $visibility !== 'GNM_SHEET_VISIBILITY_VISIBLE') { $this->spreadsheet->getActiveSheet()->setSheetState(Worksheet::SHEETSTATE_HIDDEN); } if (!$this->readDataOnly) { (new PageSetup($this->spreadsheet)) ->printInformation($sheet) ->sheetMargins($sheet); } foreach ($sheet->Cells->Cell as $cellOrNull) { $cell = self::testSimpleXml($cellOrNull); $cellAttributes = self::testSimpleXml($cell->attributes()); $row = (int) $cellAttributes->Row + 1; $column = (int) $cellAttributes->Col; $maxRow = max($maxRow, $row); $maxCol = max($maxCol, $column); $column = Coordinate::stringFromColumnIndex($column + 1); // Read cell? if ($this->getReadFilter() !== null) { if (!$this->getReadFilter()->readCell($column, $row, $worksheetName)) { continue; } } $this->loadCell($cell, $worksheetName, $cellAttributes, $column, $row); } if ($sheet->Styles !== null) { (new Styles($this->spreadsheet, $this->readDataOnly))->read($sheet, $maxRow, $maxCol); } $this->processComments($sheet); $this->processColumnWidths($sheet, $maxCol); $this->processRowHeights($sheet, $maxRow); $this->processMergedCells($sheet); $this->processAutofilter($sheet); $this->setSelectedCells($sheet); ++$worksheetID; } $this->processDefinedNames($gnmXML); $this->setSelectedSheet($gnmXML); // Return return $this->spreadsheet; } private function setSelectedSheet(SimpleXMLElement $gnmXML): void { if (isset($gnmXML->UIData)) { $attributes = self::testSimpleXml($gnmXML->UIData->attributes()); $selectedSheet = (int) $attributes['SelectedTab']; $this->spreadsheet->setActiveSheetIndex($selectedSheet); } } private function setSelectedCells(?SimpleXMLElement $sheet): void { if ($sheet !== null && isset($sheet->Selections)) { foreach ($sheet->Selections as $selection) { $startCol = (int) ($selection->StartCol ?? 0); $startRow = (int) ($selection->StartRow ?? 0) + 1; $endCol = (int) ($selection->EndCol ?? $startCol); $endRow = (int) ($selection->endRow ?? 0) + 1; $startColumn = Coordinate::stringFromColumnIndex($startCol + 1); $endColumn = Coordinate::stringFromColumnIndex($endCol + 1); $startCell = "{$startColumn}{$startRow}"; $endCell = "{$endColumn}{$endRow}"; $selectedRange = $startCell . (($endCell !== $startCell) ? ':' . $endCell : ''); $this->spreadsheet->getActiveSheet()->setSelectedCell($selectedRange); break; } } } private function processMergedCells(?SimpleXMLElement $sheet): void { // Handle Merged Cells in this worksheet if ($sheet !== null && isset($sheet->MergedRegions)) { foreach ($sheet->MergedRegions->Merge as $mergeCells) { if (strpos((string) $mergeCells, ':') !== false) { $this->spreadsheet->getActiveSheet()->mergeCells($mergeCells, Worksheet::MERGE_CELL_CONTENT_HIDE); } } } } private function processAutofilter(?SimpleXMLElement $sheet): void { if ($sheet !== null && isset($sheet->Filters)) { foreach ($sheet->Filters->Filter as $autofilter) { if ($autofilter !== null) { $attributes = $autofilter->attributes(); if (isset($attributes['Area'])) { $this->spreadsheet->getActiveSheet()->setAutoFilter((string) $attributes['Area']); } } } } } private function setColumnWidth(int $whichColumn, float $defaultWidth): void { $columnDimension = $this->spreadsheet->getActiveSheet() ->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1)); if ($columnDimension !== null) { $columnDimension->setWidth($defaultWidth); } } private function setColumnInvisible(int $whichColumn): void { $columnDimension = $this->spreadsheet->getActiveSheet() ->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1)); if ($columnDimension !== null) { $columnDimension->setVisible(false); } } private function processColumnLoop(int $whichColumn, int $maxCol, ?SimpleXMLElement $columnOverride, float $defaultWidth): int { $columnOverride = self::testSimpleXml($columnOverride); $columnAttributes = self::testSimpleXml($columnOverride->attributes()); $column = $columnAttributes['No']; $columnWidth = ((float) $columnAttributes['Unit']) / 5.4; $hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1'); $columnCount = (int) ($columnAttributes['Count'] ?? 1); while ($whichColumn < $column) { $this->setColumnWidth($whichColumn, $defaultWidth); ++$whichColumn; } while (($whichColumn < ($column + $columnCount)) && ($whichColumn <= $maxCol)) { $this->setColumnWidth($whichColumn, $columnWidth); if ($hidden) { $this->setColumnInvisible($whichColumn); } ++$whichColumn; } return $whichColumn; } private function processColumnWidths(?SimpleXMLElement $sheet, int $maxCol): void { if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Cols))) { // Column Widths $defaultWidth = 0; $columnAttributes = $sheet->Cols->attributes(); if ($columnAttributes !== null) { $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4; } $whichColumn = 0; foreach ($sheet->Cols->ColInfo as $columnOverride) { $whichColumn = $this->processColumnLoop($whichColumn, $maxCol, $columnOverride, $defaultWidth); } while ($whichColumn <= $maxCol) { $this->setColumnWidth($whichColumn, $defaultWidth); ++$whichColumn; } } } private function setRowHeight(int $whichRow, float $defaultHeight): void { $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow); if ($rowDimension !== null) { $rowDimension->setRowHeight($defaultHeight); } } private function setRowInvisible(int $whichRow): void { $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow); if ($rowDimension !== null) { $rowDimension->setVisible(false); } } private function processRowLoop(int $whichRow, int $maxRow, ?SimpleXMLElement $rowOverride, float $defaultHeight): int { $rowOverride = self::testSimpleXml($rowOverride); $rowAttributes = self::testSimpleXml($rowOverride->attributes()); $row = $rowAttributes['No']; $rowHeight = (float) $rowAttributes['Unit']; $hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1'); $rowCount = (int) ($rowAttributes['Count'] ?? 1); while ($whichRow < $row) { ++$whichRow; $this->setRowHeight($whichRow, $defaultHeight); } while (($whichRow < ($row + $rowCount)) && ($whichRow < $maxRow)) { ++$whichRow; $this->setRowHeight($whichRow, $rowHeight); if ($hidden) { $this->setRowInvisible($whichRow); } } return $whichRow; } private function processRowHeights(?SimpleXMLElement $sheet, int $maxRow): void { if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Rows))) { // Row Heights $defaultHeight = 0; $rowAttributes = $sheet->Rows->attributes(); if ($rowAttributes !== null) { $defaultHeight = (float) $rowAttributes['DefaultSizePts']; } $whichRow = 0; foreach ($sheet->Rows->RowInfo as $rowOverride) { $whichRow = $this->processRowLoop($whichRow, $maxRow, $rowOverride, $defaultHeight); } // never executed, I can't figure out any circumstances // under which it would be executed, and, even if // such exist, I'm not convinced this is needed. //while ($whichRow < $maxRow) { // ++$whichRow; // $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow)->setRowHeight($defaultHeight); //} } } private function processDefinedNames(?SimpleXMLElement $gnmXML): void { // Loop through definedNames (global named ranges) if ($gnmXML !== null && isset($gnmXML->Names)) { foreach ($gnmXML->Names->Name as $definedName) { $name = (string) $definedName->name; $value = (string) $definedName->value; if (stripos($value, '#REF!') !== false) { continue; } [$worksheetName] = Worksheet::extractSheetTitle($value, true); $worksheetName = trim($worksheetName, "'"); $worksheet = $this->spreadsheet->getSheetByName($worksheetName); // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet if ($worksheet !== null) { $this->spreadsheet->addDefinedName(DefinedName::createInstance($name, $worksheet, $value)); } } } } private function parseRichText(string $is): RichText { $value = new RichText(); $value->createText($is); return $value; } private function loadCell( SimpleXMLElement $cell, string $worksheetName, SimpleXMLElement $cellAttributes, string $column, int $row ): void { $ValueType = $cellAttributes->ValueType; $ExprID = (string) $cellAttributes->ExprID; $type = DataType::TYPE_FORMULA; if ($ExprID > '') { if (((string) $cell) > '') { $this->expressions[$ExprID] = [ 'column' => $cellAttributes->Col, 'row' => $cellAttributes->Row, 'formula' => (string) $cell, ]; } else { $expression = $this->expressions[$ExprID]; $cell = $this->referenceHelper->updateFormulaReferences( $expression['formula'], 'A1', $cellAttributes->Col - $expression['column'], $cellAttributes->Row - $expression['row'], $worksheetName ); } $type = DataType::TYPE_FORMULA; } else { $vtype = (string) $ValueType; if (array_key_exists($vtype, self::$mappings['dataType'])) { $type = self::$mappings['dataType'][$vtype]; } if ($vtype === '20') { // Boolean $cell = $cell == 'TRUE'; } } $this->spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit((string) $cell, $type); if (isset($cellAttributes->ValueFormat)) { $this->spreadsheet->getActiveSheet()->getCell($column . $row) ->getStyle()->getNumberFormat() ->setFormatCode((string) $cellAttributes->ValueFormat); } } } phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php000064400000000640152427537250017352 0ustar00 [ 'font' => [ 'bold' => true, 'size' => 24, ], ], // Bold, 24pt 'h2' => [ 'font' => [ 'bold' => true, 'size' => 18, ], ], // Bold, 18pt 'h3' => [ 'font' => [ 'bold' => true, 'size' => 13.5, ], ], // Bold, 13.5pt 'h4' => [ 'font' => [ 'bold' => true, 'size' => 12, ], ], // Bold, 12pt 'h5' => [ 'font' => [ 'bold' => true, 'size' => 10, ], ], // Bold, 10pt 'h6' => [ 'font' => [ 'bold' => true, 'size' => 7.5, ], ], // Bold, 7.5pt 'a' => [ 'font' => [ 'underline' => true, 'color' => [ 'argb' => Color::COLOR_BLUE, ], ], ], // Blue underlined 'hr' => [ 'borders' => [ 'bottom' => [ 'borderStyle' => Border::BORDER_THIN, 'color' => [ Color::COLOR_BLACK, ], ], ], ], // Bottom border 'strong' => [ 'font' => [ 'bold' => true, ], ], // Bold 'b' => [ 'font' => [ 'bold' => true, ], ], // Bold 'i' => [ 'font' => [ 'italic' => true, ], ], // Italic 'em' => [ 'font' => [ 'italic' => true, ], ], // Italic ]; /** @var array */ protected $rowspan = []; /** * Create a new HTML Reader instance. */ public function __construct() { parent::__construct(); $this->securityScanner = XmlScanner::getInstance($this); } /** * Validate that the current file is an HTML file. */ public function canRead(string $filename): bool { // Check if file exists try { $this->openFile($filename); } catch (Exception $e) { return false; } $beginning = $this->readBeginning(); $startWithTag = self::startsWithTag($beginning); $containsTags = self::containsTags($beginning); $endsWithTag = self::endsWithTag($this->readEnding()); fclose($this->fileHandle); return $startWithTag && $containsTags && $endsWithTag; } private function readBeginning(): string { fseek($this->fileHandle, 0); return (string) fread($this->fileHandle, self::TEST_SAMPLE_SIZE); } private function readEnding(): string { $meta = stream_get_meta_data($this->fileHandle); $filename = $meta['uri']; $size = (int) filesize($filename); if ($size === 0) { return ''; } $blockSize = self::TEST_SAMPLE_SIZE; if ($size < $blockSize) { $blockSize = $size; } fseek($this->fileHandle, $size - $blockSize); return (string) fread($this->fileHandle, $blockSize); } private static function startsWithTag(string $data): bool { return '<' === substr(trim($data), 0, 1); } private static function endsWithTag(string $data): bool { return '>' === substr(trim($data), -1, 1); } private static function containsTags(string $data): bool { return strlen($data) !== strlen(strip_tags($data)); } /** * Loads Spreadsheet from file. */ public function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); // Load into this instance return $this->loadIntoExisting($filename, $spreadsheet); } /** * Set input encoding. * * @param string $inputEncoding Input encoding, eg: 'ANSI' * * @return $this * * @codeCoverageIgnore * * @deprecated no use is made of this property */ public function setInputEncoding($inputEncoding) { $this->inputEncoding = $inputEncoding; return $this; } /** * Get input encoding. * * @return string * * @codeCoverageIgnore * * @deprecated no use is made of this property */ public function getInputEncoding() { return $this->inputEncoding; } // Data Array used for testing only, should write to Spreadsheet object on completion of tests /** @var array */ protected $dataArray = []; /** @var int */ protected $tableLevel = 0; /** @var array */ protected $nestedColumn = ['A']; protected function setTableStartColumn(string $column): string { if ($this->tableLevel == 0) { $column = 'A'; } ++$this->tableLevel; $this->nestedColumn[$this->tableLevel] = $column; return $this->nestedColumn[$this->tableLevel]; } protected function getTableStartColumn(): string { return $this->nestedColumn[$this->tableLevel]; } protected function releaseTableStartColumn(): string { --$this->tableLevel; return array_pop($this->nestedColumn); } /** * Flush cell. * * @param string $column * @param int|string $row * @param mixed $cellContent */ protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent, array $attributeArray): void { if (is_string($cellContent)) { // Simple String content if (trim($cellContent) > '') { // Only actually write it if there's content in the string // Write to worksheet to be done here... // ... we return the cell, so we can mess about with styles more easily // Set cell value explicitly if there is data-type attribute if (isset($attributeArray['data-type'])) { $datatype = $attributeArray['data-type']; if (in_array($datatype, [DataType::TYPE_STRING, DataType::TYPE_STRING2, DataType::TYPE_INLINE])) { //Prevent to Excel treat string with beginning equal sign or convert big numbers to scientific number if (substr($cellContent, 0, 1) === '=') { $sheet->getCell($column . $row) ->getStyle() ->setQuotePrefix(true); } } //catching the Exception and ignoring the invalid data types try { $sheet->setCellValueExplicit($column . $row, $cellContent, $attributeArray['data-type']); } catch (\PhpOffice\PhpSpreadsheet\Exception $exception) { $sheet->setCellValue($column . $row, $cellContent); } } else { $sheet->setCellValue($column . $row, $cellContent); } $this->dataArray[$row][$column] = $cellContent; } } else { // We have a Rich Text run // TODO $this->dataArray[$row][$column] = 'RICH TEXT: ' . $cellContent; } $cellContent = (string) ''; } private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void { $attributeArray = []; foreach ($child->attributes as $attribute) { $attributeArray[$attribute->name] = $attribute->value; } if ($child->nodeName === 'body') { $row = 1; $column = 'A'; $cellContent = ''; $this->tableLevel = 0; $this->processDomElement($child, $sheet, $row, $column, $cellContent); } else { $this->processDomElementTitle($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementTitle(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'title') { $this->processDomElement($child, $sheet, $row, $column, $cellContent); $sheet->setTitle($cellContent, true, true); $cellContent = ''; } else { $this->processDomElementSpanEtc($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private const SPAN_ETC = ['span', 'div', 'font', 'i', 'em', 'strong', 'b']; private function processDomElementSpanEtc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if (in_array((string) $child->nodeName, self::SPAN_ETC, true)) { if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') { $sheet->getComment($column . $row) ->getText() ->createTextRun($child->textContent); } else { $this->processDomElement($child, $sheet, $row, $column, $cellContent); } if (isset($this->formats[$child->nodeName])) { $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); } } else { $this->processDomElementHr($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementHr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'hr') { $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray); ++$row; if (isset($this->formats[$child->nodeName])) { $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); } ++$row; } // fall through to br $this->processDomElementBr($sheet, $row, $column, $cellContent, $child, $attributeArray); } private function processDomElementBr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'br' || $child->nodeName === 'hr') { if ($this->tableLevel > 0) { // If we're inside a table, replace with a \n and set the cell to wrap $cellContent .= "\n"; $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true); } else { // Otherwise flush our existing content and move the row cursor on $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray); ++$row; } } else { $this->processDomElementA($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementA(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'a') { foreach ($attributeArray as $attributeName => $attributeValue) { switch ($attributeName) { case 'href': $sheet->getCell($column . $row)->getHyperlink()->setUrl($attributeValue); if (isset($this->formats[$child->nodeName])) { $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); } break; case 'class': if ($attributeValue === 'comment-indicator') { break; // Ignore - it's just a red square. } } } // no idea why this should be needed //$cellContent .= ' '; $this->processDomElement($child, $sheet, $row, $column, $cellContent); } else { $this->processDomElementH1Etc($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private const H1_ETC = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'p']; private function processDomElementH1Etc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if (in_array((string) $child->nodeName, self::H1_ETC, true)) { if ($this->tableLevel > 0) { // If we're inside a table, replace with a \n $cellContent .= $cellContent ? "\n" : ''; $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true); $this->processDomElement($child, $sheet, $row, $column, $cellContent); } else { if ($cellContent > '') { $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray); ++$row; } $this->processDomElement($child, $sheet, $row, $column, $cellContent); $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray); if (isset($this->formats[$child->nodeName])) { $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); } ++$row; $column = 'A'; } } else { $this->processDomElementLi($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementLi(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'li') { if ($this->tableLevel > 0) { // If we're inside a table, replace with a \n $cellContent .= $cellContent ? "\n" : ''; $this->processDomElement($child, $sheet, $row, $column, $cellContent); } else { if ($cellContent > '') { $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray); } ++$row; $this->processDomElement($child, $sheet, $row, $column, $cellContent); $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray); $column = 'A'; } } else { $this->processDomElementImg($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementImg(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'img') { $this->insertImage($sheet, $column, $row, $attributeArray); } else { $this->processDomElementTable($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private string $currentColumn = 'A'; private function processDomElementTable(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'table') { $this->currentColumn = 'A'; $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray); $column = $this->setTableStartColumn($column); if ($this->tableLevel > 1 && $row > 1) { --$row; } $this->processDomElement($child, $sheet, $row, $column, $cellContent); $column = $this->releaseTableStartColumn(); if ($this->tableLevel > 1) { ++$column; } else { ++$row; } } else { $this->processDomElementTr($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementTr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'col') { $this->applyInlineStyle($sheet, -1, $this->currentColumn, $attributeArray); ++$this->currentColumn; } elseif ($child->nodeName === 'tr') { $column = $this->getTableStartColumn(); $cellContent = ''; $this->processDomElement($child, $sheet, $row, $column, $cellContent); if (isset($attributeArray['height'])) { $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']); } ++$row; } else { $this->processDomElementThTdOther($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementThTdOther(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName !== 'td' && $child->nodeName !== 'th') { $this->processDomElement($child, $sheet, $row, $column, $cellContent); } else { $this->processDomElementThTd($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementBgcolor(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['bgcolor'])) { $sheet->getStyle("$column$row")->applyFromArray( [ 'fill' => [ 'fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $this->getStyleColor($attributeArray['bgcolor'])], ], ] ); } } private function processDomElementWidth(Worksheet $sheet, string $column, array $attributeArray): void { if (isset($attributeArray['width'])) { $sheet->getColumnDimension($column)->setWidth((new CssDimension($attributeArray['width']))->width()); } } private function processDomElementHeight(Worksheet $sheet, int $row, array $attributeArray): void { if (isset($attributeArray['height'])) { $sheet->getRowDimension($row)->setRowHeight((new CssDimension($attributeArray['height']))->height()); } } private function processDomElementAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['align'])) { $sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']); } } private function processDomElementVAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['valign'])) { $sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']); } } private function processDomElementDataFormat(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['data-format'])) { $sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']); } } private function processDomElementThTd(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { while (isset($this->rowspan[$column . $row])) { ++$column; } $this->processDomElement($child, $sheet, $row, $column, $cellContent); // apply inline style $this->applyInlineStyle($sheet, $row, $column, $attributeArray); $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray); $this->processDomElementBgcolor($sheet, $row, $column, $attributeArray); $this->processDomElementWidth($sheet, $column, $attributeArray); $this->processDomElementHeight($sheet, $row, $attributeArray); $this->processDomElementAlign($sheet, $row, $column, $attributeArray); $this->processDomElementVAlign($sheet, $row, $column, $attributeArray); $this->processDomElementDataFormat($sheet, $row, $column, $attributeArray); if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { //create merging rowspan and colspan $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { ++$columnTo; } $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1); foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) { $this->rowspan[$value] = true; } $sheet->mergeCells($range); $column = $columnTo; } elseif (isset($attributeArray['rowspan'])) { //create merging rowspan $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1); foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) { $this->rowspan[$value] = true; } $sheet->mergeCells($range); } elseif (isset($attributeArray['colspan'])) { //create merging colspan $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { ++$columnTo; } $sheet->mergeCells($column . $row . ':' . $columnTo . $row); $column = $columnTo; } ++$column; } protected function processDomElement(DOMNode $element, Worksheet $sheet, int &$row, string &$column, string &$cellContent): void { foreach ($element->childNodes as $child) { if ($child instanceof DOMText) { $domText = (string) preg_replace('/\s+/u', ' ', trim($child->nodeValue ?? '')); if (is_string($cellContent)) { // simply append the text if the cell content is a plain text string $cellContent .= $domText; } // but if we have a rich text run instead, we need to append it correctly // TODO } elseif ($child instanceof DOMElement) { $this->processDomElementBody($sheet, $row, $column, $cellContent, $child); } } } /** * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. * * @param string $filename * * @return Spreadsheet */ public function loadIntoExisting($filename, Spreadsheet $spreadsheet) { // Validate if (!$this->canRead($filename)) { throw new Exception($filename . ' is an Invalid HTML file.'); } // Create a new DOM object $dom = new DOMDocument(); // Reload the HTML file into the DOM object try { $convert = $this->getSecurityScannerOrThrow()->scanFile($filename); $lowend = "\u{80}"; $highend = "\u{10ffff}"; $regexp = "/[$lowend-$highend]/u"; /** @var callable */ $callback = [self::class, 'replaceNonAscii']; $convert = preg_replace_callback($regexp, $callback, $convert); $loaded = ($convert === null) ? false : $dom->loadHTML($convert); } catch (Throwable $e) { $loaded = false; } if ($loaded === false) { throw new Exception('Failed to load ' . $filename . ' as a DOM Document', 0, $e ?? null); } self::loadProperties($dom, $spreadsheet); return $this->loadDocument($dom, $spreadsheet); } private static function loadProperties(DOMDocument $dom, Spreadsheet $spreadsheet): void { $properties = $spreadsheet->getProperties(); foreach ($dom->getElementsByTagName('meta') as $meta) { $metaContent = (string) $meta->getAttribute('content'); if ($metaContent !== '') { $metaName = (string) $meta->getAttribute('name'); switch ($metaName) { case 'author': $properties->setCreator($metaContent); break; case 'category': $properties->setCategory($metaContent); break; case 'company': $properties->setCompany($metaContent); break; case 'created': $properties->setCreated($metaContent); break; case 'description': $properties->setDescription($metaContent); break; case 'keywords': $properties->setKeywords($metaContent); break; case 'lastModifiedBy': $properties->setLastModifiedBy($metaContent); break; case 'manager': $properties->setManager($metaContent); break; case 'modified': $properties->setModified($metaContent); break; case 'subject': $properties->setSubject($metaContent); break; case 'title': $properties->setTitle($metaContent); break; default: if (preg_match('/^custom[.](bool|date|float|int|string)[.](.+)$/', $metaName, $matches) === 1) { switch ($matches[1]) { case 'bool': $properties->setCustomProperty($matches[2], (bool) $metaContent, Properties::PROPERTY_TYPE_BOOLEAN); break; case 'float': $properties->setCustomProperty($matches[2], (float) $metaContent, Properties::PROPERTY_TYPE_FLOAT); break; case 'int': $properties->setCustomProperty($matches[2], (int) $metaContent, Properties::PROPERTY_TYPE_INTEGER); break; case 'date': $properties->setCustomProperty($matches[2], $metaContent, Properties::PROPERTY_TYPE_DATE); break; default: // string $properties->setCustomProperty($matches[2], $metaContent, Properties::PROPERTY_TYPE_STRING); } } } } } if (!empty($dom->baseURI)) { $properties->setHyperlinkBase($dom->baseURI); } } private static function replaceNonAscii(array $matches): string { return '&#' . mb_ord($matches[0], 'UTF-8') . ';'; } /** * Spreadsheet from content. * * @param string $content */ public function loadFromString($content, ?Spreadsheet $spreadsheet = null): Spreadsheet { // Create a new DOM object $dom = new DOMDocument(); // Reload the HTML file into the DOM object try { $convert = $this->getSecurityScannerOrThrow()->scan($content); $lowend = "\u{80}"; $highend = "\u{10ffff}"; $regexp = "/[$lowend-$highend]/u"; /** @var callable */ $callback = [self::class, 'replaceNonAscii']; $convert = preg_replace_callback($regexp, $callback, $convert); $loaded = ($convert === null) ? false : $dom->loadHTML($convert); } catch (Throwable $e) { $loaded = false; } if ($loaded === false) { throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null); } $spreadsheet = $spreadsheet ?? new Spreadsheet(); self::loadProperties($dom, $spreadsheet); return $this->loadDocument($dom, $spreadsheet); } /** * Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance. */ private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet { while ($spreadsheet->getSheetCount() <= $this->sheetIndex) { $spreadsheet->createSheet(); } $spreadsheet->setActiveSheetIndex($this->sheetIndex); // Discard white space $document->preserveWhiteSpace = false; $row = 0; $column = 'A'; $content = ''; $this->rowspan = []; $this->processDomElement($document, $spreadsheet->getActiveSheet(), $row, $column, $content); // Return return $spreadsheet; } /** * Get sheet index. * * @return int */ public function getSheetIndex() { return $this->sheetIndex; } /** * Set sheet index. * * @param int $sheetIndex Sheet index * * @return $this */ public function setSheetIndex($sheetIndex) { $this->sheetIndex = $sheetIndex; return $this; } /** * Apply inline css inline style. * * NOTES : * Currently only intended for td & th element, * and only takes 'background-color' and 'color'; property with HEX color * * TODO : * - Implement to other propertie, such as border * * @param int $row * @param string $column * @param array $attributeArray */ private function applyInlineStyle(Worksheet &$sheet, $row, $column, $attributeArray): void { if (!isset($attributeArray['style'])) { return; } if ($row <= 0 || $column === '') { $cellStyle = new Style(); } elseif (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { ++$columnTo; } $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1); $cellStyle = $sheet->getStyle($range); } elseif (isset($attributeArray['rowspan'])) { $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1); $cellStyle = $sheet->getStyle($range); } elseif (isset($attributeArray['colspan'])) { $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { ++$columnTo; } $range = $column . $row . ':' . $columnTo . $row; $cellStyle = $sheet->getStyle($range); } else { $cellStyle = $sheet->getStyle($column . $row); } // add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color $styles = explode(';', $attributeArray['style']); foreach ($styles as $st) { $value = explode(':', $st); $styleName = isset($value[0]) ? trim($value[0]) : null; $styleValue = isset($value[1]) ? trim($value[1]) : null; $styleValueString = (string) $styleValue; if (!$styleName) { continue; } switch ($styleName) { case 'background': case 'background-color': $styleColor = $this->getStyleColor($styleValueString); if (!$styleColor) { continue 2; } $cellStyle->applyFromArray(['fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $styleColor]]]); break; case 'color': $styleColor = $this->getStyleColor($styleValueString); if (!$styleColor) { continue 2; } $cellStyle->applyFromArray(['font' => ['color' => ['rgb' => $styleColor]]]); break; case 'border': $this->setBorderStyle($cellStyle, $styleValueString, 'allBorders'); break; case 'border-top': $this->setBorderStyle($cellStyle, $styleValueString, 'top'); break; case 'border-bottom': $this->setBorderStyle($cellStyle, $styleValueString, 'bottom'); break; case 'border-left': $this->setBorderStyle($cellStyle, $styleValueString, 'left'); break; case 'border-right': $this->setBorderStyle($cellStyle, $styleValueString, 'right'); break; case 'font-size': $cellStyle->getFont()->setSize( (float) $styleValue ); break; case 'font-weight': if ($styleValue === 'bold' || $styleValue >= 500) { $cellStyle->getFont()->setBold(true); } break; case 'font-style': if ($styleValue === 'italic') { $cellStyle->getFont()->setItalic(true); } break; case 'font-family': $cellStyle->getFont()->setName(str_replace('\'', '', $styleValueString)); break; case 'text-decoration': switch ($styleValue) { case 'underline': $cellStyle->getFont()->setUnderline(Font::UNDERLINE_SINGLE); break; case 'line-through': $cellStyle->getFont()->setStrikethrough(true); break; } break; case 'text-align': $cellStyle->getAlignment()->setHorizontal($styleValueString); break; case 'vertical-align': $cellStyle->getAlignment()->setVertical($styleValueString); break; case 'width': if ($column !== '') { $sheet->getColumnDimension($column)->setWidth( (new CssDimension($styleValue ?? ''))->width() ); } break; case 'height': if ($row > 0) { $sheet->getRowDimension($row)->setRowHeight( (new CssDimension($styleValue ?? ''))->height() ); } break; case 'word-wrap': $cellStyle->getAlignment()->setWrapText( $styleValue === 'break-word' ); break; case 'text-indent': $cellStyle->getAlignment()->setIndent( (int) str_replace(['px'], '', $styleValueString) ); break; } } } /** * Check if has #, so we can get clean hex. * * @param mixed $value * * @return null|string */ public function getStyleColor($value) { $value = (string) $value; if (strpos($value, '#') === 0) { return substr($value, 1); } return \PhpOffice\PhpSpreadsheet\Helper\Html::colourNameLookup($value); } /** * @param string $column * @param int $row */ private function insertImage(Worksheet $sheet, $column, $row, array $attributes): void { if (!isset($attributes['src'])) { return; } $src = urldecode($attributes['src']); $width = isset($attributes['width']) ? (float) $attributes['width'] : null; $height = isset($attributes['height']) ? (float) $attributes['height'] : null; $name = $attributes['alt'] ?? null; $drawing = new Drawing(); $drawing->setPath($src); $drawing->setWorksheet($sheet); $drawing->setCoordinates($column . $row); $drawing->setOffsetX(0); $drawing->setOffsetY(10); $drawing->setResizeProportional(true); if ($name) { $drawing->setName($name); } if ($width) { $drawing->setWidth((int) $width); } if ($height) { $drawing->setHeight((int) $height); } $sheet->getColumnDimension($column)->setWidth( $drawing->getWidth() / 6 ); $sheet->getRowDimension($row)->setRowHeight( $drawing->getHeight() * 0.9 ); } private const BORDER_MAPPINGS = [ 'dash-dot' => Border::BORDER_DASHDOT, 'dash-dot-dot' => Border::BORDER_DASHDOTDOT, 'dashed' => Border::BORDER_DASHED, 'dotted' => Border::BORDER_DOTTED, 'double' => Border::BORDER_DOUBLE, 'hair' => Border::BORDER_HAIR, 'medium' => Border::BORDER_MEDIUM, 'medium-dashed' => Border::BORDER_MEDIUMDASHED, 'medium-dash-dot' => Border::BORDER_MEDIUMDASHDOT, 'medium-dash-dot-dot' => Border::BORDER_MEDIUMDASHDOTDOT, 'none' => Border::BORDER_NONE, 'slant-dash-dot' => Border::BORDER_SLANTDASHDOT, 'solid' => Border::BORDER_THIN, 'thick' => Border::BORDER_THICK, ]; public static function getBorderMappings(): array { return self::BORDER_MAPPINGS; } /** * Map html border style to PhpSpreadsheet border style. * * @param string $style * * @return null|string */ public function getBorderStyle($style) { return self::BORDER_MAPPINGS[$style] ?? null; } /** * @param string $styleValue * @param string $type */ private function setBorderStyle(Style $cellStyle, $styleValue, $type): void { if (trim($styleValue) === Border::BORDER_NONE) { $borderStyle = Border::BORDER_NONE; $color = null; } else { $borderArray = explode(' ', $styleValue); $borderCount = count($borderArray); if ($borderCount >= 3) { $borderStyle = $borderArray[1]; $color = $borderArray[2]; } else { $borderStyle = $borderArray[0]; $color = $borderArray[1] ?? null; } } $cellStyle->applyFromArray([ 'borders' => [ $type => [ 'borderStyle' => $this->getBorderStyle($borderStyle), 'color' => ['rgb' => $this->getStyleColor($color)], ], ], ]); } } phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php000064400000000731152427537250020547 0ustar00securityScanner = XmlScanner::getInstance($this); } /** * Can the current IReader read the file? */ public function canRead(string $filename): bool { $mimeType = 'UNKNOWN'; // Load file if (File::testFileNoThrow($filename, '')) { $zip = new ZipArchive(); if ($zip->open($filename) === true) { // check if it is an OOXML archive $stat = $zip->statName('mimetype'); if (!empty($stat) && ($stat['size'] <= 255)) { $mimeType = $zip->getFromName($stat['name']); } elseif ($zip->statName('META-INF/manifest.xml')) { $xml = simplexml_load_string( $this->getSecurityScannerOrThrow() ->scan( $zip->getFromName( 'META-INF/manifest.xml' ) ) ); if ($xml !== false) { $namespacesContent = $xml->getNamespaces(true); if (isset($namespacesContent['manifest'])) { $manifest = $xml->children($namespacesContent['manifest']); foreach ($manifest as $manifestDataSet) { /** @scrutinizer ignore-call */ $manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']); if ($manifestAttributes && $manifestAttributes->{'full-path'} == '/') { $mimeType = (string) $manifestAttributes->{'media-type'}; break; } } } } } $zip->close(); } } return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet'; } /** * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object. * * @param string $filename * * @return string[] */ public function listWorksheetNames($filename) { File::assertFile($filename, self::INITIAL_FILE); $worksheetNames = []; $xml = new XMLReader(); $xml->xml( $this->getSecurityScannerOrThrow() ->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE) ); $xml->setParserProperty(2, true); // Step into the first level of content of the XML $xml->read(); while ($xml->read()) { // Quickly jump through to the office:body node while (self::getXmlName($xml) !== 'office:body') { if ($xml->isEmptyElement) { $xml->read(); } else { $xml->next(); } } // Now read each node until we find our first table:table node while ($xml->read()) { $xmlName = self::getXmlName($xml); if ($xmlName == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) { // Loop through each table:table node reading the table:name attribute for each worksheet name do { $worksheetName = $xml->getAttribute('table:name'); if (!empty($worksheetName)) { $worksheetNames[] = $worksheetName; } $xml->next(); } while (self::getXmlName($xml) == 'table:table' && $xml->nodeType == XMLReader::ELEMENT); } } } return $worksheetNames; } /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * * @param string $filename * * @return array */ public function listWorksheetInfo($filename) { File::assertFile($filename, self::INITIAL_FILE); $worksheetInfo = []; $xml = new XMLReader(); $xml->xml( $this->getSecurityScannerOrThrow() ->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE) ); $xml->setParserProperty(2, true); // Step into the first level of content of the XML $xml->read(); while ($xml->read()) { // Quickly jump through to the office:body node while (self::getXmlName($xml) !== 'office:body') { if ($xml->isEmptyElement) { $xml->read(); } else { $xml->next(); } } // Now read each node until we find our first table:table node while ($xml->read()) { if (self::getXmlName($xml) == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) { $worksheetNames[] = $xml->getAttribute('table:name'); $tmpInfo = [ 'worksheetName' => $xml->getAttribute('table:name'), 'lastColumnLetter' => 'A', 'lastColumnIndex' => 0, 'totalRows' => 0, 'totalColumns' => 0, ]; // Loop through each child node of the table:table element reading $currCells = 0; do { $xml->read(); if (self::getXmlName($xml) == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) { $rowspan = $xml->getAttribute('table:number-rows-repeated'); $rowspan = empty($rowspan) ? 1 : $rowspan; $tmpInfo['totalRows'] += $rowspan; $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); $currCells = 0; // Step into the row $xml->read(); do { $doread = true; if (self::getXmlName($xml) == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) { if (!$xml->isEmptyElement) { ++$currCells; $xml->next(); $doread = false; } } elseif (self::getXmlName($xml) == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) { $mergeSize = $xml->getAttribute('table:number-columns-repeated'); $currCells += (int) $mergeSize; } if ($doread) { $xml->read(); } } while (self::getXmlName($xml) != 'table:table-row'); } } while (self::getXmlName($xml) != 'table:table'); $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1; $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); $worksheetInfo[] = $tmpInfo; } } } return $worksheetInfo; } /** * Counteract Phpstan caching. * * @phpstan-impure */ private static function getXmlName(XMLReader $xml): string { return $xml->name; } /** * Loads PhpSpreadsheet from file. */ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); // Load into this instance return $this->loadIntoExisting($filename, $spreadsheet); } /** * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. * * @param string $filename * * @return Spreadsheet */ public function loadIntoExisting($filename, Spreadsheet $spreadsheet) { File::assertFile($filename, self::INITIAL_FILE); $zip = new ZipArchive(); $zip->open($filename); // Meta $xml = @simplexml_load_string( $this->getSecurityScannerOrThrow() ->scan($zip->getFromName('meta.xml')) ); if ($xml === false) { throw new Exception('Unable to read data from {$pFilename}'); } $namespacesMeta = $xml->getNamespaces(true); (new DocumentProperties($spreadsheet))->load($xml, $namespacesMeta); // Styles $dom = new DOMDocument('1.01', 'UTF-8'); $dom->loadXML( $this->getSecurityScannerOrThrow() ->scan($zip->getFromName('styles.xml')) ); $pageSettings = new PageSettings($dom); // Main Content $dom = new DOMDocument('1.01', 'UTF-8'); $dom->loadXML( $this->getSecurityScannerOrThrow() ->scan($zip->getFromName(self::INITIAL_FILE)) ); $officeNs = $dom->lookupNamespaceUri('office'); $tableNs = $dom->lookupNamespaceUri('table'); $textNs = $dom->lookupNamespaceUri('text'); $xlinkNs = $dom->lookupNamespaceUri('xlink'); $styleNs = $dom->lookupNamespaceUri('style'); $pageSettings->readStyleCrossReferences($dom); $autoFilterReader = new AutoFilter($spreadsheet, $tableNs); $definedNameReader = new DefinedNames($spreadsheet, $tableNs); $columnWidths = []; $automaticStyle0 = $dom->getElementsByTagNameNS($officeNs, 'automatic-styles')->item(0); $automaticStyles = ($automaticStyle0 === null) ? [] : $automaticStyle0->getElementsByTagNameNS($styleNs, 'style'); foreach ($automaticStyles as $automaticStyle) { $styleName = $automaticStyle->getAttributeNS($styleNs, 'name'); $styleFamily = $automaticStyle->getAttributeNS($styleNs, 'family'); if ($styleFamily === 'table-column') { $tcprops = $automaticStyle->getElementsByTagNameNS($styleNs, 'table-column-properties'); if ($tcprops !== null) { $tcprop = $tcprops->item(0); if ($tcprop !== null) { $columnWidth = $tcprop->getAttributeNs($styleNs, 'column-width'); $columnWidths[$styleName] = $columnWidth; } } } } // Content $item0 = $dom->getElementsByTagNameNS($officeNs, 'body')->item(0); $spreadsheets = ($item0 === null) ? [] : $item0->getElementsByTagNameNS($officeNs, 'spreadsheet'); foreach ($spreadsheets as $workbookData) { /** @var DOMElement $workbookData */ $tables = $workbookData->getElementsByTagNameNS($tableNs, 'table'); $worksheetID = 0; foreach ($tables as $worksheetDataSet) { /** @var DOMElement $worksheetDataSet */ $worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name'); // Check loadSheetsOnly if ( $this->loadSheetsOnly !== null && $worksheetName && !in_array($worksheetName, $this->loadSheetsOnly) ) { continue; } $worksheetStyleName = $worksheetDataSet->getAttributeNS($tableNs, 'style-name'); // Create sheet if ($worksheetID > 0) { $spreadsheet->createSheet(); // First sheet is added by default } $spreadsheet->setActiveSheetIndex($worksheetID); if ($worksheetName || is_numeric($worksheetName)) { // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in // formula cells... during the load, all formulae should be correct, and we're simply // bringing the worksheet name in line with the formula, not the reverse $spreadsheet->getActiveSheet()->setTitle((string) $worksheetName, false, false); } // Go through every child of table element $rowID = 1; $tableColumnIndex = 1; foreach ($worksheetDataSet->childNodes as $childNode) { /** @var DOMElement $childNode */ // Filter elements which are not under the "table" ns if ($childNode->namespaceURI != $tableNs) { continue; } $key = $childNode->nodeName; // Remove ns from node name if (strpos($key, ':') !== false) { $keyChunks = explode(':', $key); $key = array_pop($keyChunks); } switch ($key) { case 'table-header-rows': /// TODO :: Figure this out. This is only a partial implementation I guess. // ($rowData it's not used at all and I'm not sure that PHPExcel // has an API for this) // foreach ($rowData as $keyRowData => $cellData) { // $rowData = $cellData; // break; // } break; case 'table-column': if ($childNode->hasAttributeNS($tableNs, 'number-columns-repeated')) { $rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-columns-repeated'); } else { $rowRepeats = 1; } $tableStyleName = $childNode->getAttributeNS($tableNs, 'style-name'); if (isset($columnWidths[$tableStyleName])) { $columnWidth = new HelperDimension($columnWidths[$tableStyleName]); $tableColumnString = Coordinate::stringFromColumnIndex($tableColumnIndex); for ($rowRepeats2 = $rowRepeats; $rowRepeats2 > 0; --$rowRepeats2) { $spreadsheet->getActiveSheet() ->getColumnDimension($tableColumnString) ->setWidth($columnWidth->toUnit('cm'), 'cm'); ++$tableColumnString; } } $tableColumnIndex += $rowRepeats; break; case 'table-row': if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) { $rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-rows-repeated'); } else { $rowRepeats = 1; } $columnID = 'A'; /** @var DOMElement $cellData */ foreach ($childNode->childNodes as $cellData) { if ($this->getReadFilter() !== null) { if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) { if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) { $colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated'); } else { $colRepeats = 1; } for ($i = 0; $i < $colRepeats; ++$i) { ++$columnID; } continue; } } // Initialize variables $formatting = $hyperlink = null; $hasCalculatedValue = false; $cellDataFormula = ''; if ($cellData->hasAttributeNS($tableNs, 'formula')) { $cellDataFormula = $cellData->getAttributeNS($tableNs, 'formula'); $hasCalculatedValue = true; } // Annotations $annotation = $cellData->getElementsByTagNameNS($officeNs, 'annotation'); if ($annotation->length > 0 && $annotation->item(0) !== null) { $textNode = $annotation->item(0)->getElementsByTagNameNS($textNs, 'p'); if ($textNode->length > 0 && $textNode->item(0) !== null) { $text = $this->scanElementForText($textNode->item(0)); $spreadsheet->getActiveSheet() ->getComment($columnID . $rowID) ->setText($this->parseRichText($text)); // ->setAuthor( $author ) } } // Content /** @var DOMElement[] $paragraphs */ $paragraphs = []; foreach ($cellData->childNodes as $item) { /** @var DOMElement $item */ // Filter text:p elements if ($item->nodeName == 'text:p') { $paragraphs[] = $item; } } if (count($paragraphs) > 0) { // Consolidate if there are multiple p records (maybe with spans as well) $dataArray = []; // Text can have multiple text:p and within those, multiple text:span. // text:p newlines, but text:span does not. // Also, here we assume there is no text data is span fields are specified, since // we have no way of knowing proper positioning anyway. foreach ($paragraphs as $pData) { $dataArray[] = $this->scanElementForText($pData); } $allCellDataText = implode("\n", $dataArray); $type = $cellData->getAttributeNS($officeNs, 'value-type'); switch ($type) { case 'string': $type = DataType::TYPE_STRING; $dataValue = $allCellDataText; foreach ($paragraphs as $paragraph) { $link = $paragraph->getElementsByTagNameNS($textNs, 'a'); if ($link->length > 0 && $link->item(0) !== null) { $hyperlink = $link->item(0)->getAttributeNS($xlinkNs, 'href'); } } break; case 'boolean': $type = DataType::TYPE_BOOL; $dataValue = ($allCellDataText == 'TRUE') ? true : false; break; case 'percentage': $type = DataType::TYPE_NUMERIC; $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value'); // percentage should always be float //if (floor($dataValue) == $dataValue) { // $dataValue = (int) $dataValue; //} $formatting = NumberFormat::FORMAT_PERCENTAGE_00; break; case 'currency': $type = DataType::TYPE_NUMERIC; $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value'); if (floor($dataValue) == $dataValue) { $dataValue = (int) $dataValue; } $formatting = NumberFormat::FORMAT_CURRENCY_USD_INTEGER; break; case 'float': $type = DataType::TYPE_NUMERIC; $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value'); if (floor($dataValue) == $dataValue) { if ($dataValue == (int) $dataValue) { $dataValue = (int) $dataValue; } } break; case 'date': $type = DataType::TYPE_NUMERIC; $value = $cellData->getAttributeNS($officeNs, 'date-value'); $dataValue = Date::convertIsoDate($value); if ($dataValue != floor($dataValue)) { $formatting = NumberFormat::FORMAT_DATE_XLSX15 . ' ' . NumberFormat::FORMAT_DATE_TIME4; } else { $formatting = NumberFormat::FORMAT_DATE_XLSX15; } break; case 'time': $type = DataType::TYPE_NUMERIC; $timeValue = $cellData->getAttributeNS($officeNs, 'time-value'); $dataValue = Date::PHPToExcel( strtotime( '01-01-1970 ' . implode(':', /** @scrutinizer ignore-type */ sscanf($timeValue, 'PT%dH%dM%dS') ?? []) ) ); $formatting = NumberFormat::FORMAT_DATE_TIME4; break; default: $dataValue = null; } } else { $type = DataType::TYPE_NULL; $dataValue = null; } if ($hasCalculatedValue) { $type = DataType::TYPE_FORMULA; $cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1); $cellDataFormula = FormulaTranslator::convertToExcelFormulaValue($cellDataFormula); } if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) { $colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated'); } else { $colRepeats = 1; } if ($type !== null) { for ($i = 0; $i < $colRepeats; ++$i) { if ($i > 0) { ++$columnID; } if ($type !== DataType::TYPE_NULL) { for ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) { $rID = $rowID + $rowAdjust; $cell = $spreadsheet->getActiveSheet() ->getCell($columnID . $rID); // Set value if ($hasCalculatedValue) { $cell->setValueExplicit($cellDataFormula, $type); } else { $cell->setValueExplicit($dataValue, $type); } if ($hasCalculatedValue) { $cell->setCalculatedValue($dataValue); } // Set other properties if ($formatting !== null) { $spreadsheet->getActiveSheet() ->getStyle($columnID . $rID) ->getNumberFormat() ->setFormatCode($formatting); } else { $spreadsheet->getActiveSheet() ->getStyle($columnID . $rID) ->getNumberFormat() ->setFormatCode(NumberFormat::FORMAT_GENERAL); } if ($hyperlink !== null) { $cell->getHyperlink() ->setUrl($hyperlink); } } } } } // Merged cells $this->processMergedCells($cellData, $tableNs, $type, $columnID, $rowID, $spreadsheet); ++$columnID; } $rowID += $rowRepeats; break; } } $pageSettings->setVisibilityForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName); $pageSettings->setPrintSettingsForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName); ++$worksheetID; } $autoFilterReader->read($workbookData); $definedNameReader->read($workbookData); } $spreadsheet->setActiveSheetIndex(0); if ($zip->locateName('settings.xml') !== false) { $this->processSettings($zip, $spreadsheet); } // Return return $spreadsheet; } private function processSettings(ZipArchive $zip, Spreadsheet $spreadsheet): void { $dom = new DOMDocument('1.01', 'UTF-8'); $dom->loadXML( $this->getSecurityScannerOrThrow() ->scan($zip->getFromName('settings.xml')) ); //$xlinkNs = $dom->lookupNamespaceUri('xlink'); $configNs = $dom->lookupNamespaceUri('config'); //$oooNs = $dom->lookupNamespaceUri('ooo'); $officeNs = $dom->lookupNamespaceUri('office'); $settings = $dom->getElementsByTagNameNS($officeNs, 'settings') ->item(0); if ($settings !== null) { $this->lookForActiveSheet($settings, $spreadsheet, $configNs); $this->lookForSelectedCells($settings, $spreadsheet, $configNs); } } private function lookForActiveSheet(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void { /** @var DOMElement $t */ foreach ($settings->getElementsByTagNameNS($configNs, 'config-item') as $t) { if ($t->getAttributeNs($configNs, 'name') === 'ActiveTable') { try { $spreadsheet->setActiveSheetIndexByName($t->nodeValue ?? ''); } catch (Throwable $e) { // do nothing } break; } } } private function lookForSelectedCells(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void { /** @var DOMElement $t */ foreach ($settings->getElementsByTagNameNS($configNs, 'config-item-map-named') as $t) { if ($t->getAttributeNs($configNs, 'name') === 'Tables') { foreach ($t->getElementsByTagNameNS($configNs, 'config-item-map-entry') as $ws) { $setRow = $setCol = ''; $wsname = $ws->getAttributeNs($configNs, 'name'); foreach ($ws->getElementsByTagNameNS($configNs, 'config-item') as $configItem) { $attrName = $configItem->getAttributeNs($configNs, 'name'); if ($attrName === 'CursorPositionX') { $setCol = $configItem->nodeValue; } if ($attrName === 'CursorPositionY') { $setRow = $configItem->nodeValue; } } $this->setSelected($spreadsheet, $wsname, "$setCol", "$setRow"); } break; } } } private function setSelected(Spreadsheet $spreadsheet, string $wsname, string $setCol, string $setRow): void { if (is_numeric($setCol) && is_numeric($setRow)) { $sheet = $spreadsheet->getSheetByName($wsname); if ($sheet !== null) { $sheet->setSelectedCells([(int) $setCol + 1, (int) $setRow + 1]); } } } /** * Recursively scan element. * * @return string */ protected function scanElementForText(DOMNode $element) { $str = ''; foreach ($element->childNodes as $child) { /** @var DOMNode $child */ if ($child->nodeType == XML_TEXT_NODE) { $str .= $child->nodeValue; } elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:s') { // It's a space // Multiple spaces? $attributes = $child->attributes; /** @var ?DOMAttr $cAttr */ $cAttr = ($attributes === null) ? null : $attributes->getNamedItem('c'); $multiplier = self::getMultiplier($cAttr); $str .= str_repeat(' ', $multiplier); } if ($child->hasChildNodes()) { $str .= $this->scanElementForText($child); } } return $str; } private static function getMultiplier(?DOMAttr $cAttr): int { if ($cAttr) { $multiplier = (int) $cAttr->nodeValue; } else { $multiplier = 1; } return $multiplier; } /** * @param string $is * * @return RichText */ private function parseRichText($is) { $value = new RichText(); $value->createText($is); return $value; } private function processMergedCells( DOMElement $cellData, string $tableNs, string $type, string $columnID, int $rowID, Spreadsheet $spreadsheet ): void { if ( $cellData->hasAttributeNS($tableNs, 'number-columns-spanned') || $cellData->hasAttributeNS($tableNs, 'number-rows-spanned') ) { if (($type !== DataType::TYPE_NULL) || ($this->readDataOnly === false)) { $columnTo = $columnID; if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) { $columnIndex = Coordinate::columnIndexFromString($columnID); $columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned'); $columnIndex -= 2; $columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1); } $rowTo = $rowID; if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) { $rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1; } $cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo; $spreadsheet->getActiveSheet()->mergeCells($cellRange, Worksheet::MERGE_CELL_CONTENT_HIDE); } } } } phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php000064400000053740152427537250015764 0ustar00inputEncoding = $encoding; return $this; } public function getInputEncoding(): string { return $this->inputEncoding; } public function setFallbackEncoding(string $fallbackEncoding): self { $this->fallbackEncoding = $fallbackEncoding; return $this; } public function getFallbackEncoding(): string { return $this->fallbackEncoding; } /** * Move filepointer past any BOM marker. */ protected function skipBOM(): void { rewind($this->fileHandle); if (fgets($this->fileHandle, self::UTF8_BOM_LEN + 1) !== self::UTF8_BOM) { rewind($this->fileHandle); } } /** * Identify any separator that is explicitly set in the file. */ protected function checkSeparator(): void { $line = fgets($this->fileHandle); if ($line === false) { return; } if ((strlen(trim($line, "\r\n")) == 5) && (stripos($line, 'sep=') === 0)) { $this->delimiter = substr($line, 4, 1); return; } $this->skipBOM(); } /** * Infer the separator if it isn't explicitly set in the file or specified by the user. */ protected function inferSeparator(): void { if ($this->delimiter !== null) { return; } $inferenceEngine = new Delimiter($this->fileHandle, $this->escapeCharacter ?? self::$defaultEscapeCharacter, $this->enclosure); // If number of lines is 0, nothing to infer : fall back to the default if ($inferenceEngine->linesCounted() === 0) { $this->delimiter = $inferenceEngine->getDefaultDelimiter(); $this->skipBOM(); return; } $this->delimiter = $inferenceEngine->infer(); // If no delimiter could be detected, fall back to the default if ($this->delimiter === null) { $this->delimiter = $inferenceEngine->getDefaultDelimiter(); } $this->skipBOM(); } /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). */ public function listWorksheetInfo(string $filename): array { // Open file $this->openFileOrMemory($filename); $fileHandle = $this->fileHandle; // Skip BOM, if any $this->skipBOM(); $this->checkSeparator(); $this->inferSeparator(); $worksheetInfo = []; $worksheetInfo[0]['worksheetName'] = 'Worksheet'; $worksheetInfo[0]['lastColumnLetter'] = 'A'; $worksheetInfo[0]['lastColumnIndex'] = 0; $worksheetInfo[0]['totalRows'] = 0; $worksheetInfo[0]['totalColumns'] = 0; // Loop through each line of the file in turn $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); while (is_array($rowData)) { ++$worksheetInfo[0]['totalRows']; $worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1); $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); } $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1); $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1; // Close file fclose($fileHandle); return $worksheetInfo; } /** * Loads Spreadsheet from file. */ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); // Load into this instance return $this->loadIntoExisting($filename, $spreadsheet); } /** * Loads Spreadsheet from string. */ public function loadSpreadsheetFromString(string $contents): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); // Load into this instance return $this->loadStringOrFile('data://text/plain,' . urlencode($contents), $spreadsheet, true); } private function openFileOrMemory(string $filename): void { // Open file $fhandle = $this->canRead($filename); if (!$fhandle) { throw new Exception($filename . ' is an Invalid Spreadsheet file.'); } if ($this->inputEncoding === 'UTF-8') { $encoding = self::guessEncodingBom($filename); if ($encoding !== '') { $this->inputEncoding = $encoding; } } if ($this->inputEncoding === self::GUESS_ENCODING) { $this->inputEncoding = self::guessEncoding($filename, $this->fallbackEncoding); } $this->openFile($filename); if ($this->inputEncoding !== 'UTF-8') { fclose($this->fileHandle); $entireFile = file_get_contents($filename); $fileHandle = fopen('php://memory', 'r+b'); if ($fileHandle !== false && $entireFile !== false) { $this->fileHandle = $fileHandle; $data = StringHelper::convertEncoding($entireFile, 'UTF-8', $this->inputEncoding); fwrite($this->fileHandle, $data); $this->skipBOM(); } } } public function setTestAutoDetect(bool $value): self { $this->testAutodetect = $value; return $this; } private function setAutoDetect(?string $value): ?string { $retVal = null; if ($value !== null && $this->testAutodetect && PHP_VERSION_ID < 90000) { $retVal2 = @ini_set('auto_detect_line_endings', $value); if (is_string($retVal2)) { $retVal = $retVal2; } } return $retVal; } public function castFormattedNumberToNumeric( bool $castFormattedNumberToNumeric, bool $preserveNumericFormatting = false ): void { $this->castFormattedNumberToNumeric = $castFormattedNumberToNumeric; $this->preserveNumericFormatting = $preserveNumericFormatting; } /** * Open data uri for reading. */ private function openDataUri(string $filename): void { $fileHandle = fopen($filename, 'rb'); if ($fileHandle === false) { // @codeCoverageIgnoreStart throw new ReaderException('Could not open file ' . $filename . ' for reading.'); // @codeCoverageIgnoreEnd } $this->fileHandle = $fileHandle; } /** * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. */ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet { return $this->loadStringOrFile($filename, $spreadsheet, false); } /** * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. */ private function loadStringOrFile(string $filename, Spreadsheet $spreadsheet, bool $dataUri): Spreadsheet { // Deprecated in Php8.1 $iniset = $this->setAutoDetect('1'); try { $this->loadStringOrFile2($filename, $spreadsheet, $dataUri); $this->setAutoDetect($iniset); } catch (Throwable $e) { $this->setAutoDetect($iniset); throw $e; } return $spreadsheet; } private function loadStringOrFile2(string $filename, Spreadsheet $spreadsheet, bool $dataUri): void { // Open file if ($dataUri) { $this->openDataUri($filename); } else { $this->openFileOrMemory($filename); } $fileHandle = $this->fileHandle; // Skip BOM, if any $this->skipBOM(); $this->checkSeparator(); $this->inferSeparator(); // Create new PhpSpreadsheet object while ($spreadsheet->getSheetCount() <= $this->sheetIndex) { $spreadsheet->createSheet(); } $sheet = $spreadsheet->setActiveSheetIndex($this->sheetIndex); // Set our starting row based on whether we're in contiguous mode or not $currentRow = 1; $outRow = 0; // Loop through each line of the file in turn $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); $valueBinder = Cell::getValueBinder(); $preserveBooleanString = method_exists($valueBinder, 'getBooleanConversion') && $valueBinder->getBooleanConversion(); while (is_array($rowData)) { $noOutputYet = true; $columnLetter = 'A'; foreach ($rowData as $rowDatum) { $this->convertBoolean($rowDatum, $preserveBooleanString); $numberFormatMask = $this->convertFormattedNumber($rowDatum); if (($rowDatum !== '' || $this->preserveNullString) && $this->readFilter->readCell($columnLetter, $currentRow)) { if ($this->contiguous) { if ($noOutputYet) { $noOutputYet = false; ++$outRow; } } else { $outRow = $currentRow; } // Set basic styling for the value (Note that this could be overloaded by styling in a value binder) $sheet->getCell($columnLetter . $outRow)->getStyle() ->getNumberFormat() ->setFormatCode($numberFormatMask); // Set cell value $sheet->getCell($columnLetter . $outRow)->setValue($rowDatum); } ++$columnLetter; } $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); ++$currentRow; } // Close file fclose($fileHandle); } /** * Convert string true/false to boolean, and null to null-string. * * @param mixed $rowDatum */ private function convertBoolean(&$rowDatum, bool $preserveBooleanString): void { if (is_string($rowDatum) && !$preserveBooleanString) { if (strcasecmp(Calculation::getTRUE(), $rowDatum) === 0 || strcasecmp('true', $rowDatum) === 0) { $rowDatum = true; } elseif (strcasecmp(Calculation::getFALSE(), $rowDatum) === 0 || strcasecmp('false', $rowDatum) === 0) { $rowDatum = false; } } else { $rowDatum = $rowDatum ?? ''; } } /** * Convert numeric strings to int or float values. * * @param mixed $rowDatum */ private function convertFormattedNumber(&$rowDatum): string { $numberFormatMask = NumberFormat::FORMAT_GENERAL; if ($this->castFormattedNumberToNumeric === true && is_string($rowDatum)) { $numeric = str_replace( [StringHelper::getThousandsSeparator(), StringHelper::getDecimalSeparator()], ['', '.'], $rowDatum ); if (is_numeric($numeric)) { $decimalPos = strpos($rowDatum, StringHelper::getDecimalSeparator()); if ($this->preserveNumericFormatting === true) { $numberFormatMask = (strpos($rowDatum, StringHelper::getThousandsSeparator()) !== false) ? '#,##0' : '0'; if ($decimalPos !== false) { $decimals = strlen($rowDatum) - $decimalPos - 1; $numberFormatMask .= '.' . str_repeat('0', min($decimals, 6)); } } $rowDatum = ($decimalPos !== false) ? (float) $numeric : (int) $numeric; } } return $numberFormatMask; } public function getDelimiter(): ?string { return $this->delimiter; } public function setDelimiter(?string $delimiter): self { $this->delimiter = $delimiter; return $this; } public function getEnclosure(): string { return $this->enclosure; } public function setEnclosure(string $enclosure): self { if ($enclosure == '') { $enclosure = '"'; } $this->enclosure = $enclosure; return $this; } public function getSheetIndex(): int { return $this->sheetIndex; } public function setSheetIndex(int $indexValue): self { $this->sheetIndex = $indexValue; return $this; } public function setContiguous(bool $contiguous): self { $this->contiguous = $contiguous; return $this; } public function getContiguous(): bool { return $this->contiguous; } /** * Php9 intends to drop support for this parameter in fgetcsv. * Not yet ready to mark deprecated in order to give users * a migration path. */ public function setEscapeCharacter(string $escapeCharacter): self { if (PHP_VERSION_ID >= 90000 && $escapeCharacter !== '') { throw new ReaderException('Escape character must be null string for Php9+'); } $this->escapeCharacter = $escapeCharacter; return $this; } public function getEscapeCharacter(): string { return $this->escapeCharacter ?? self::$defaultEscapeCharacter; } /** * Can the current IReader read the file? */ public function canRead(string $filename): bool { // Check if file exists try { $this->openFile($filename); } catch (ReaderException $e) { return false; } fclose($this->fileHandle); // Trust file extension if any $extension = strtolower(/** @scrutinizer ignore-type */ pathinfo($filename, PATHINFO_EXTENSION)); if (in_array($extension, ['csv', 'tsv'])) { return true; } // Attempt to guess mimetype $type = mime_content_type($filename); $supportedTypes = [ 'application/csv', 'text/csv', 'text/plain', 'inode/x-empty', ]; return in_array($type, $supportedTypes, true); } private static function guessEncodingTestNoBom(string &$encoding, string &$contents, string $compare, string $setEncoding): void { if ($encoding === '') { $pos = strpos($contents, $compare); if ($pos !== false && $pos % strlen($compare) === 0) { $encoding = $setEncoding; } } } private static function guessEncodingNoBom(string $filename): string { $encoding = ''; $contents = file_get_contents($filename); self::guessEncodingTestNoBom($encoding, $contents, self::UTF32BE_LF, 'UTF-32BE'); self::guessEncodingTestNoBom($encoding, $contents, self::UTF32LE_LF, 'UTF-32LE'); self::guessEncodingTestNoBom($encoding, $contents, self::UTF16BE_LF, 'UTF-16BE'); self::guessEncodingTestNoBom($encoding, $contents, self::UTF16LE_LF, 'UTF-16LE'); if ($encoding === '' && preg_match('//u', $contents) === 1) { $encoding = 'UTF-8'; } return $encoding; } private static function guessEncodingTestBom(string &$encoding, string $first4, string $compare, string $setEncoding): void { if ($encoding === '') { if ($compare === substr($first4, 0, strlen($compare))) { $encoding = $setEncoding; } } } public static function guessEncodingBom(string $filename, ?string $convertString = null): string { $encoding = ''; $first4 = $convertString ?? (string) file_get_contents($filename, false, null, 0, 4); self::guessEncodingTestBom($encoding, $first4, self::UTF8_BOM, 'UTF-8'); self::guessEncodingTestBom($encoding, $first4, self::UTF16BE_BOM, 'UTF-16BE'); self::guessEncodingTestBom($encoding, $first4, self::UTF32BE_BOM, 'UTF-32BE'); self::guessEncodingTestBom($encoding, $first4, self::UTF32LE_BOM, 'UTF-32LE'); self::guessEncodingTestBom($encoding, $first4, self::UTF16LE_BOM, 'UTF-16LE'); return $encoding; } public static function guessEncoding(string $filename, string $dflt = self::DEFAULT_FALLBACK_ENCODING): string { $encoding = self::guessEncodingBom($filename); if ($encoding === '') { $encoding = self::guessEncodingNoBom($filename); } return ($encoding === '') ? $dflt : $encoding; } public function setPreserveNullString(bool $value): self { $this->preserveNullString = $value; return $this; } public function getPreserveNullString(): bool { return $this->preserveNullString; } /** * Php8.4 deprecates use of anything other than null string * as escape Character. * * @param resource $stream * * @return array|false */ private static function getCsv( $stream, ?int $length = null, string $separator = ',', string $enclosure = '"', ?string $escape = null ) { $escape = $escape ?? self::$defaultEscapeCharacter; if (PHP_VERSION_ID >= 80400 && $escape !== '') { return @fgetcsv($stream, $length, $separator, $enclosure, $escape); } return fgetcsv($stream, $length, $separator, $enclosure, $escape); } public static function affectedByPhp9( string $filename, string $inputEncoding = 'UTF-8', ?string $delimiter = null, string $enclosure = '"', string $escapeCharacter = '\\' ): bool { if (PHP_VERSION_ID < 70400 || PHP_VERSION_ID >= 90000) { throw new ReaderException('Function valid only for Php7.4 or Php8'); // @codeCoverageIgnore } $reader1 = new self(); $reader1->setInputEncoding($inputEncoding) ->setTestAutoDetect(true) ->setEscapeCharacter($escapeCharacter) ->setDelimiter($delimiter) ->setEnclosure($enclosure); $spreadsheet1 = $reader1->load($filename); $sheet1 = $spreadsheet1->getActiveSheet(); $array1 = $sheet1->toArray(null, false, false); $spreadsheet1->disconnectWorksheets(); $reader2 = new self(); $reader2->setInputEncoding($inputEncoding) ->setTestAutoDetect(false) ->setEscapeCharacter('') ->setDelimiter($delimiter) ->setEnclosure($enclosure); $spreadsheet2 = $reader2->load($filename); $sheet2 = $spreadsheet2->getActiveSheet(); $array2 = $sheet2->toArray(null, false, false); $spreadsheet2->disconnectWorksheets(); return $array1 !== $array2; } } phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php000064400000004255152427537250017172 0ustar00 | // | 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 */ class File extends PPS { /** * The constructor. * * @param string $name The name of the file (in Unicode) * * @see OLE::ascToUcs() */ public function __construct($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(). * * @return mixed true on success */ public function init() { return true; } /** * Append data to PPS. * * @param string $data The data to append */ public function append($data): void { $this->_data .= $data; } } phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php000064400000035273152427537250017242 0ustar00 | // | 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 */ class Root extends PPS { /** * @var resource */ private $fileHandle; /** * @var ?int */ private $smallBlockSize; /** * @var ?int */ private $bigBlockSize; /** * @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, $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) { $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(&$raList) { // 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 * * @return float * * @see save() */ private static function adjust2($i2) { $iWk = log($i2) / log(2); return ($iWk > floor($iWk)) ? floor($iWk) + 1 : $iWk; } /** * Save OLE header. * * @param int $iSBDcnt * @param int $iBBcnt * @param int $iPPScnt */ private function saveHeader($iSBDcnt, $iBBcnt, $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 int $iStBlk * @param array $raList Reference to array of PPS's */ private function saveBigData($iStBlk, &$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 * * @return string */ private function makeSmallData(&$raList) { $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(&$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. * * @param int $iSbdSize * @param int $iBsize * @param int $iPpsCnt */ private function saveBbd($iSbdSize, $iBsize, $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.php000064400000016031152427537250016306 0ustar00 | // | Based on OLE::Storage_Lite by Kawai, Takanori | // +----------------------------------------------------------------------+ // use PhpOffice\PhpSpreadsheet\Shared\OLE; /** * Class for creating PPS's for OLE containers. * * @author Xavier Noguer */ class PPS { /** * The PPS index. * * @var int */ public $No; /** * The PPS name (in Unicode). * * @var string */ public $Name; /** * The PPS type. Dir, Root or File. * * @var int */ public $Type; /** * The index of the previous PPS. * * @var int */ public $PrevPps; /** * The index of the next PPS. * * @var int */ public $NextPps; /** * The index of it's first child if this is a Dir or Root PPS. * * @var int */ public $DirPps; /** * A timestamp. * * @var float|int */ public $Time1st; /** * A timestamp. * * @var float|int */ public $Time2nd; /** * Starting block (small or big) for this PPS's data inside the container. * * @var ?int */ public $startBlock; /** * The size of the PPS's data (in bytes). * * @var int */ public $Size; /** * The PPS's data (only used if it's not using a temporary file). * * @var string */ public $_data = ''; /** * Array of child PPS's (only used by Root and Dir PPS's). * * @var array */ public $children = []; /** * Pointer to OLE container. * * @var OLE */ public $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($No, $name, $type, $prev, $next, $dir, $time_1st, $time_2nd, $data, $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() { //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() { $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 * @param mixed $to_save * @param mixed $depth * * @return int The index for this PPS */ public static function savePpsSetPnt(&$raList, $to_save, $depth = 0) { if (!is_array($to_save) || (empty($to_save))) { return 0xFFFFFFFF; } 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 = 0xFFFFFFFF; $raList[$cnt]->NextPps = 0xFFFFFFFF; $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/OLE/ChainedBlockStream.php000064400000013474152427537250021336 0ustar00params); 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($count) // @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() // @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(). * * @return int */ public function stream_tell() // @codingStandardsIgnoreLine { return $this->pos; } /** * Implements support for fseek(). * * @param int $offset byte offset * @param int $whence SEEK_SET, SEEK_CUR or SEEK_END * * @return bool */ public function stream_seek($offset, $whence) // @codingStandardsIgnoreLine { if ($whence == SEEK_SET && $offset >= 0) { $this->pos = $offset; } elseif ($whence == SEEK_CUR && -$offset <= $this->pos) { $this->pos += $offset; // @phpstan-ignore-next-line } elseif ($whence == SEEK_END && -$offset <= count(/** @scrutinizer ignore-type */ $this->data)) { $this->pos = strlen($this->data) + $offset; } else { return false; } return true; } /** * Implements support for fstat(). Currently the only supported field is * "size". * * @return array */ public function stream_stat() // @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/Trend/ExponentialBestFit.php000064400000006030152427537250022046 0ustar00getIntersect() * $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($yValue) { 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 * * @return string */ public function getEquation($dp = 0) { $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 * * @return float */ public function getSlope($dp = 0) { 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 * * @return float */ public function getIntersect($dp = 0) { 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( function ($value) { return ($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 * @param bool $const */ public function __construct($yValues, $xValues = [], $const = true) { parent::__construct($yValues, $xValues); if (!$this->error) { $this->exponentialRegression($yValues, $xValues, (bool) $const); } } } phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php000064400000004134152427537250020775 0ustar00getIntersect() + $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($yValue) { 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 * * @return string */ public function getEquation($dp = 0) { $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 * @param bool $const */ public function __construct($yValues, $xValues = [], $const = true) { parent::__construct($yValues, $xValues); if (!$this->error) { $this->linearRegression($yValues, $xValues, (bool) $const); } } } phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php000064400000011624152427537250017360 0ustar00getGoodnessOfFit(); } 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/PolynomialBestFit.php000064400000014555152427537250021716 0ustar00slope 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). * * @var string */ protected $bestFitType = 'polynomial'; /** * Polynomial order. * * @var int */ protected $order = 0; /** * Return the order of this polynomial. * * @return int */ public function getOrder() { 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($xValue) { $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($yValue) { 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 * * @return string */ public function getEquation($dp = 0) { $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 * * @return float */ public function getSlope($dp = 0) { if ($dp != 0) { $coefficients = []; // Scrutinizer is correct - $this->slope is float, not array. //* @phpstan-ignore-next-line foreach ($this->slope as $coefficient) { $coefficients[] = round($coefficient, $dp); } // @phpstan-ignore-next-line return $coefficients; } return $this->slope; } /** * @param int $dp * * @return array */ public function getCoefficients($dp = 0) { // 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($order, $yValues, $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 (and maybe Scrutinizer) are 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($order, $yValues, $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/Trend/LogarithmicBestFit.php000064400000004532152427537250022027 0ustar00getIntersect() + $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($yValue) { 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 * * @return string */ public function getEquation($dp = 0) { $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( function ($value) { return ($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 * @param bool $const */ public function __construct($yValues, $xValues = [], $const = true) { parent::__construct($yValues, $xValues); if (!$this->error) { $this->logarithmicRegression($yValues, $xValues, (bool) $const); } } } phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php000064400000005572152427537250020666 0ustar00getIntersect() * ($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($yValue) { 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 * * @return string */ public function getEquation($dp = 0) { $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 * * @return float */ public function getIntersect($dp = 0) { 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( function ($value) { return ($value < 0.0) ? 0 - log(abs($value)) : log($value); }, $yValues ); $adjustedXValues = array_map( function ($value) { return ($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 * @param bool $const */ public function __construct($yValues, $xValues = [], $const = true) { parent::__construct($yValues, $xValues); if (!$this->error) { $this->powerRegression($yValues, $xValues, (bool) $const); } } } phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php000064400000030306152427537250017642 0ustar00error; } /** @return string */ public function getBestFitType() { 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($xValue); /** * Return the X-Value for a specified value of Y. * * @param float $yValue Y-Value * * @return float X-Value */ abstract public function getValueOfXForY($yValue); /** * Return the original set of X-Values. * * @return float[] X-Values */ public function getXValues() { return $this->xValues; } /** * Return the Equation of the best-fit line. * * @param int $dp Number of places of decimal precision to display * * @return string */ abstract public function getEquation($dp = 0); /** * Return the Slope of the line. * * @param int $dp Number of places of decimal precision to display * * @return float */ public function getSlope($dp = 0) { 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 * * @return float */ public function getSlopeSE($dp = 0) { 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 * * @return float */ public function getIntersect($dp = 0) { 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 * * @return float */ public function getIntersectSE($dp = 0) { 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 * * @return float */ public function getGoodnessOfFit($dp = 0) { 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 * * @return float */ public function getGoodnessOfFitPercent($dp = 0) { 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 * * @return float */ public function getStdevOfResiduals($dp = 0) { if ($dp != 0) { return round($this->stdevOfResiduals, $dp); } return $this->stdevOfResiduals; } /** * @param int $dp Number of places of decimal precision to return * * @return float */ public function getSSRegression($dp = 0) { if ($dp != 0) { return round($this->SSRegression, $dp); } return $this->SSRegression; } /** * @param int $dp Number of places of decimal precision to return * * @return float */ public function getSSResiduals($dp = 0) { if ($dp != 0) { return round($this->SSResiduals, $dp); } return $this->SSResiduals; } /** * @param int $dp Number of places of decimal precision to return * * @return float */ public function getDFResiduals($dp = 0) { if ($dp != 0) { return round($this->DFResiduals, $dp); } return $this->DFResiduals; } /** * @param int $dp Number of places of decimal precision to return * * @return float */ public function getF($dp = 0) { if ($dp != 0) { return round($this->f, $dp); } return $this->f; } /** * @param int $dp Number of places of decimal precision to return * * @return float */ public function getCovariance($dp = 0) { if ($dp != 0) { return round($this->covariance, $dp); } return $this->covariance; } /** * @param int $dp Number of places of decimal precision to return * * @return float */ public function getCorrelation($dp = 0) { if ($dp != 0) { return round($this->correlation, $dp); } return $this->correlation; } /** * @return float[] */ public function getYBestFitValues() { return $this->yBestFitValues; } /** @var mixed */ private static $scrutinizerZeroPointZero = 0.0; /** * @param mixed $x * @param mixed $y */ private static function scrutinizerLooseCompare($x, $y): bool { return $x == $y; } /** * @param float $sumX * @param float $sumY * @param float $sumX2 * @param float $sumY2 * @param float $sumXY * @param float $meanX * @param float $meanY * @param bool|int $const */ protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $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); } // Scrutinizer thinks $SSres == $SStot is always true. It is wrong. if ($SStot == self::$scrutinizerZeroPointZero || self::scrutinizerLooseCompare($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( function ($value) { return $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($yValues, $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/Escher/DgContainer/SpgrContainer/SpContainer.php000064400000016451152427537250025644 0ustar00parent = $parent; } /** * Get the parent Shape Group Container. * * @return SpgrContainer */ public function getParent() { return $this->parent; } /** * Set whether this is a group shape. * * @param bool $value */ public function setSpgr($value): void { $this->spgr = $value; } /** * Get whether this is a group shape. * * @return bool */ public function getSpgr() { return $this->spgr; } /** * Set the shape type. * * @param int $value */ public function setSpType($value): void { $this->spType = $value; } /** * Get the shape type. * * @return int */ public function getSpType() { return $this->spType; } /** * Set the shape flag. * * @param int $value */ public function setSpFlag($value): void { $this->spFlag = $value; } /** * Get the shape flag. * * @return int */ public function getSpFlag() { return $this->spFlag; } /** * Set the shape index. * * @param int $value */ public function setSpId($value): void { $this->spId = $value; } /** * Get the shape index. * * @return int */ public function getSpId() { return $this->spId; } /** * Set an option for the Shape Group Container. * * @param int $property The number specifies the option * @param mixed $value */ public function setOPT($property, $value): void { $this->OPT[$property] = $value; } /** * Get an option for the Shape Group Container. * * @param int $property The number specifies the option * * @return mixed */ public function getOPT($property) { if (isset($this->OPT[$property])) { return $this->OPT[$property]; } return null; } /** * Get the collection of options. * * @return array */ public function getOPTCollection() { return $this->OPT; } /** * Set cell coordinates of upper-left corner of shape. * * @param string $value eg: 'A1' */ public function setStartCoordinates($value): void { $this->startCoordinates = $value; } /** * Get cell coordinates of upper-left corner of shape. * * @return string */ public function getStartCoordinates() { return $this->startCoordinates; } /** * Set offset in x-direction of upper-left corner of shape measured in 1/1024 of column width. * * @param int $startOffsetX */ public function setStartOffsetX($startOffsetX): void { $this->startOffsetX = $startOffsetX; } /** * Get offset in x-direction of upper-left corner of shape measured in 1/1024 of column width. * * @return int */ public function getStartOffsetX() { return $this->startOffsetX; } /** * Set offset in y-direction of upper-left corner of shape measured in 1/256 of row height. * * @param int $startOffsetY */ public function setStartOffsetY($startOffsetY): void { $this->startOffsetY = $startOffsetY; } /** * Get offset in y-direction of upper-left corner of shape measured in 1/256 of row height. * * @return int */ public function getStartOffsetY() { return $this->startOffsetY; } /** * Set cell coordinates of bottom-right corner of shape. * * @param string $value eg: 'A1' */ public function setEndCoordinates($value): void { $this->endCoordinates = $value; } /** * Get cell coordinates of bottom-right corner of shape. * * @return string */ public function getEndCoordinates() { return $this->endCoordinates; } /** * Set offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width. * * @param int $endOffsetX */ public function setEndOffsetX($endOffsetX): void { $this->endOffsetX = $endOffsetX; } /** * Get offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width. * * @return int */ public function getEndOffsetX() { return $this->endOffsetX; } /** * Set offset in y-direction of bottom-right corner of shape measured in 1/256 of row height. * * @param int $endOffsetY */ public function setEndOffsetY($endOffsetY): void { $this->endOffsetY = $endOffsetY; } /** * Get offset in y-direction of bottom-right corner of shape measured in 1/256 of row height. * * @return int */ public function getEndOffsetY() { 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() { $nestingLevel = 0; $parent = $this->getParent(); while ($parent instanceof SpgrContainer) { ++$nestingLevel; $parent = $parent->getParent(); } return $nestingLevel; } } phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php000064400000002775152427537250023423 0ustar00parent = $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. * * @param mixed $child */ public function addChild($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() { $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/DggContainer/BstoreContainer/BSE/Blip.php000064400000001624152427537250025404 0ustar00data; } /** * Set the raw image data. * * @param string $data */ public function setData($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.php000064400000003211152427537250024510 0ustar00parent = $parent; } /** * Get the BLIP. * * @return ?BSE\Blip */ public function getBlip() { return $this->blip; } /** * Set the BLIP. */ public function setBlip(BSE\Blip $blip): void { $this->blip = $blip; $blip->setParent($this); } /** * Get the BLIP type. * * @return int */ public function getBlipType() { return $this->blipType; } /** * Set the BLIP type. * * @param int $blipType */ public function setBlipType($blipType): void { $this->blipType = $blipType; } } phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php000064400000001224152427537250024101 0ustar00BSECollection[] = $BSE; $BSE->setParent($this); } /** * Get the collection of BLIP Store Entries. * * @return BstoreContainer\BSE[] */ public function getBSECollection() { return $this->BSECollection; } } phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php000064400000006444152427537250021011 0ustar00spIdMax; } /** * Set maximum shape index of all shapes in all drawings (plus one). * * @param int $value */ public function setSpIdMax($value): void { $this->spIdMax = $value; } /** * Get total number of drawings saved. * * @return int */ public function getCDgSaved() { return $this->cDgSaved; } /** * Set total number of drawings saved. * * @param int $value */ public function setCDgSaved($value): void { $this->cDgSaved = $value; } /** * Get total number of shapes saved (including group shapes). * * @return int */ public function getCSpSaved() { return $this->cSpSaved; } /** * Set total number of shapes saved (including group shapes). * * @param int $value */ public function setCSpSaved($value): void { $this->cSpSaved = $value; } /** * Get BLIP Store Container. * * @return ?DggContainer\BstoreContainer */ public function getBstoreContainer() { return $this->bstoreContainer; } /** * Set BLIP Store Container. * * @param DggContainer\BstoreContainer $bstoreContainer */ public function setBstoreContainer($bstoreContainer): void { $this->bstoreContainer = $bstoreContainer; } /** * Set an option for the drawing group. * * @param int $property The number specifies the option * @param mixed $value */ public function setOPT($property, $value): void { $this->OPT[$property] = $value; } /** * Get an option for the drawing group. * * @param int $property The number specifies the option * * @return mixed */ public function getOPT($property) { if (isset($this->OPT[$property])) { return $this->OPT[$property]; } return null; } /** * Get identifier clusters. * * @return array */ public function getIDCLs() { return $this->IDCLs; } /** * Set identifier clusters. [ => , ...]. * * @param array $IDCLs */ public function setIDCLs($IDCLs): void { $this->IDCLs = $IDCLs; } } phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php000064400000002500152427537250020627 0ustar00dgId; } 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(): ?DgContainer\SpgrContainer { return $this->spgrContainer; } public function getSpgrContainerOrThrow(): DgContainer\SpgrContainer { if ($this->spgrContainer !== null) { return $this->spgrContainer; } throw new SpreadsheetException('spgrContainer is unexpectedly null'); } /** @param DgContainer\SpgrContainer $spgrContainer */ public function setSpgrContainer($spgrContainer): DgContainer\SpgrContainer { return $this->spgrContainer = $spgrContainer; } } phpspreadsheet/src/PhpSpreadsheet/Shared/File.php000064400000014003152427537250016101 0ustar00open($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 = 0; $i < $iMax; ++$i) { if ($pathArray[$i] == '..' && $i > 0) { unset($pathArray[$i], $pathArray[$i - 1]); 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/Escher.php000064400000002213152427537250016433 0ustar00dggContainer; } /** * Set Drawing Group Container. * * @param Escher\DggContainer $dggContainer * * @return Escher\DggContainer */ public function setDggContainer($dggContainer) { return $this->dggContainer = $dggContainer; } /** * Get Drawing Container. * * @return ?Escher\DgContainer */ public function getDgContainer() { return $this->dgContainer; } /** * Set Drawing Container. * * @param Escher\DgContainer $dgContainer * * @return Escher\DgContainer */ public function setDgContainer($dgContainer) { return $this->dgContainer = $dgContainer; } } phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php000064400000004223152427537250016757 0ustar00setTimeZone(new DateTimeZone($timezoneName)); return $dtobj->getOffset(); } } phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php000064400000011115152427537250016672 0ustar00 '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/Drawing.php000064400000011572152427537250016625 0ustar00getName(); $size = $defaultFont->getSize(); if (isset(Font::$defaultColumnWidths[$name][$size])) { // Exact width can be determined return $pixelValue * Font::$defaultColumnWidths[$name][$size]['width'] / Font::$defaultColumnWidths[$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::$defaultColumnWidths['Calibri'][11]['width'] / Font::$defaultColumnWidths['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($cellWidth, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont) { // Font name and size $name = $defaultFont->getName(); $size = $defaultFont->getSize(); if (isset(Font::$defaultColumnWidths[$name][$size])) { // Exact width can be determined $colWidth = $cellWidth * Font::$defaultColumnWidths[$name][$size]['px'] / Font::$defaultColumnWidths[$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::$defaultColumnWidths['Calibri'][11]['px'] / Font::$defaultColumnWidths['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($pixelValue) { return $pixelValue * 0.75; } /** * Convert points to pixels. * * @param int $pointValue Value in points * * @return int Value in pixels */ public static function pointsToPixels($pointValue) { 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($degrees) { return (int) round($degrees * 60000); } /** * Convert angle to degrees. * * @param int|SimpleXMLElement $angle Angle * * @return int Degrees */ public static function angleToDegrees($angle) { $angle = (int) $angle; if ($angle != 0) { return (int) round($angle / 60000); } return 0; } /** * Create a new image from file. By alexander at alexauto dot nl. * * @see http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214 * * @param string $bmpFilename Path to Windows DIB (BMP) image * * @return GdImage|resource * * @deprecated 1.26 use Php function imagecreatefrombmp instead * * @codeCoverageIgnore */ public static function imagecreatefrombmp($bmpFilename) { $retVal = @imagecreatefrombmp($bmpFilename); if ($retVal === false) { throw new ReaderException("Unable to create image from $bmpFilename"); } return $retVal; } } phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php000064400000045512152427537250016110 0ustar00 '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 $numberSuffixes = [ 'st', 'nd', 'rd', 'th', ]; /** * Base calendar year to use for calculations * Value is either CALENDAR_WINDOWS_1900 (1900) or CALENDAR_MAC_1904 (1904). * * @var int */ protected static $excelCalendar = self::CALENDAR_WINDOWS_1900; /** * Default timezone to use for DateTime objects. * * @var null|DateTimeZone */ protected static $defaultTimeZone; /** * 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($baseYear) { 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() { 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) { try { $timeZone = self::validateTimeZone($timeZone); self::$defaultTimeZone = $timeZone; $retval = true; } catch (PhpSpreadsheetException $e) { $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) { 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. * * @return float|int */ public static function convertIsoDate($value) { 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($excelTimestamp, $timeZone = null) { $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; $hours = floor($partDay * 24); $partDay = $partDay * 24 - $hours; $minutes = floor($partDay * 60); $partDay = $partDay * 60 - $minutes; $seconds = round($partDay * 60); if ($days >= 0) { $days = '+' . $days; } $interval = $days . ' days'; return $baseDate->modify($interval) ->setTime((int) $hours, (int) $minutes, (int) $seconds); } /** * 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) { return (int) self::excelToDateTimeObject($excelTimestamp, $timeZone) ->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($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) { return self::formattedPHPToExcel( (int) $dateValue->format('Y'), (int) $dateValue->format('m'), (int) $dateValue->format('d'), (int) $dateValue->format('H'), (int) $dateValue->format('i'), (int) $dateValue->format('s') ); } /** * 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) { if (!is_numeric($unixTimestamp)) { return false; } return self::dateTimeToExcel(new DateTime('@' . $unixTimestamp)); } /** * formattedPHPToExcel. * * @param int $year * @param int $month * @param int $day * @param int $hours * @param int $minutes * @param int $seconds * * @return float Excel date/time value */ public static function formattedPHPToExcel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0) { 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? * * @param mixed $value * * @return bool */ public static function isDateTime(Cell $cell, $value = null, bool $dateWithoutTimeOkay = true) { $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 $e) { // 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? * * @return bool */ public static function isDateTimeFormat(NumberFormat $excelFormatCode, bool $dateWithoutTimeOkay = true) { 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? * * @param string $excelFormatCode * * @return bool */ public static function isDateTimeFormatCode($excelFormatCode, bool $dateWithoutTimeOkay = true) { 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 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 ((substr($excelFormatCode, 0, 1) == '_') || (substr($excelFormatCode, 0, 2) == '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 (\strpos($excelFormatCode, '-00000') !== false) { 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 (strpos($excelFormatCode, '"') !== false) { $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($dateValue) { 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 (strpos($dateValue, ':') !== false) { $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($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($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); } } phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php000064400000026773152427537250016011 0ustar00getParentOrThrow()->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, $row = 1) { // 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 string $startColumn * @param int $startOffsetX Offset within start cell measured in 1/1024 of the cell width * @param string $endColumn * @param 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, $startColumn = 'A', $startOffsetX = 0, $endColumn = 'A', $endOffsetX = 0) { $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 int $startOffsetY Offset within start cell measured in 1/256 of the cell height * @param int $endRow (1-based) * @param 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, $startRow = 1, $startOffsetY = 0, $endRow = 1, $endOffsetY = 0) { $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 * * @return null|array */ public static function oneAnchor2twoAnchor(Worksheet $worksheet, $coordinates, $offsetX, $offsetY, $width, $height) { [$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/PasswordHasher.php000064400000007654152427537250020175 0ustar00 '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 . * * Scrutinizer will squawk at the use of bitwise operations here, * but it should ultimately pass. * * @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 . /** @scrutinizer ignore-type */ $encodedPassword, true); for ($i = 0; $i < $spinCount; ++$i) { $hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true); } return base64_encode($hashValue); } } phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php000064400000056157152427537250017650 0ustar00 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. * * @return bool */ public static function getIsIconvEnabled() { 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 () * element or in the shared string element. * * @param string $textValue Value to unescape * * @return string */ public static function controlCharacterOOXML2PHP($textValue) { 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 () * element or in the shared string element. * * @param string $textValue Value to escape * * @return string */ public static function controlCharacterPHP2OOXML($textValue) { 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(/** @scrutinizer ignore-type */ $subst); return self::returnString($returnValue); } /** * Strictly to satisfy Scrutinizer. * * @param mixed $value */ private static function returnString($value): string { return is_string($value) ? $value : ''; } /** * 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. * * @param float|int|string $numericValue */ public static function formatNumber($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 mixed[] $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 { // character count $ln = self::countCharacters($textValue, 'UTF-8'); // characters $chars = self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8'); 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 self::returnString(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('/(? $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). * * @param string $textValue * * @return mixed string or only the leading numeric part of the string */ public static function testStringAsNumeric($textValue) { if (is_numeric($textValue)) { return $textValue; } $v = (float) $textValue; return (is_numeric(substr($textValue, 0, strlen((string) $v)))) ? $v : $textValue; } } phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php000064400000005053152427537250017064 0ustar00openMemory(); } 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 != '') { /** @scrutinizer ignore-unhandled */ @unlink($this->tempFileName); } } public function __wakeup(): void { $this->tempFileName = ''; throw new SpreadsheetException('Unserialize not permitted'); } /** * Get written data. * * @return string */ public function getData() { if ($this->tempFileName == '') { return $this->outputMemory(true); } $this->flush(); return file_get_contents($this->tempFileName) ?: ''; } /** * Wrapper method for writeRaw. * * @param null|string|string[] $rawTextData * * @return bool */ public function writeRawData($rawTextData) { if (is_array($rawTextData)) { $rawTextData = implode("\n", $rawTextData); } return $this->writeRaw(htmlspecialchars($rawTextData ?? '')); } } phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php000064400000060066152427537250016142 0ustar00 [ '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 */ private static $extraFontArray = []; public static function setExtraFontArray(array $extraFontArray): void { self::$extraFontArray = $extraFontArray; } public static function getExtraFontArray(): array { return self::$extraFontArray; } /** * AutoSize method. * * @var string */ private static $autoSizeMethod = self::AUTOSIZE_METHOD_APPROX; /** * Path to folder containing TrueType font .ttf files. * * @var string */ private static $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], ], ]; /** * List of column widths. Replaced by constant; * previously it was public and updateable, allowing * user to make inappropriate alterations. * * @deprecated 1.25.0 Use DEFAULT_COLUMN_WIDTHS constant instead. * * @var array */ public static $defaultColumnWidths = self::DEFAULT_COLUMN_WIDTHS; /** * Set autoSize method. * * @param string $method see self::AUTOSIZE_METHOD_* * * @return bool Success or failure */ public static function setAutoSizeMethod($method) { if (!in_array($method, self::AUTOSIZE_METHODS)) { return false; } self::$autoSizeMethod = $method; return true; } /** * Get autoSize method. * * @return string */ public static function getAutoSizeMethod() { return self::$autoSizeMethod; } /** * Set the path to the folder containing .ttf files. There should be a trailing slash. * Typical locations on variout some platforms: *
    *
  • C:/Windows/Fonts/
  • *
  • /usr/share/fonts/truetype/
  • *
  • ~/.fonts/
  • *
. * * @param string $folderPath */ public static function setTrueTypeFontPath($folderPath): void { self::$trueTypeFontPath = $folderPath; } /** * Get the path to the folder containing .ttf files. * * @return string */ public static function getTrueTypeFontPath() { return self::$trueTypeFontPath; } /** * 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 = '', $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 (strpos($cellText, "\n") !== false) { $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) + $columnWidthAdjust; } catch (PhpSpreadsheetException $e) { $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. * * @param string $columnText * @param int $rotation * * @return int Text width in pixels (no padding added) */ public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $rotation = 0) { $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 int $fontSizeInPoints Font size (in points) * * @return int Font size (in pixels) */ public static function fontSizeToPixels($fontSizeInPoints) { return (int) ((4 / 3) * $fontSizeInPoints); } /** * Calculate an (approximate) pixel size, based on inch size. * * @param int $sizeInInch Font size (in inch) * * @return int Size (in pixels) */ public static function inchSizeToPixels($sizeInInch) { return $sizeInInch * 96; } /** * Calculate an (approximate) pixel size, based on centimeter size. * * @param int $sizeInCm Font size (in centimeters) * * @return float Size (in pixels) */ public static function centimeterSizeToPixels($sizeInCm) { 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) { 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::$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($fontName) { 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 mixed Column width */ public static function getDefaultColumnWidthByFont(FontStyle $font, $returnAsPixels = false) { 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) { $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; } } phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php000064400000042743152427537250015655 0ustar00 | // | 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 * @author Christian Schmidt */ 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. * * @var array */ public $_list = []; /** * Root directory of OLE container. * * @var Root */ public $root; /** * Big Block Allocation Table. * * @var array (blockId => nextBlockId) */ public $bbat; /** * Short Block Allocation Table. * * @var array (blockId => nextBlockId) */ public $sbat; /** * Size of big blocks. This is usually 512. * * @var int number of octets per block */ public $bigBlockSize; /** * Size of small blocks. This is usually 64. * * @var int number of octets per block */ public $smallBlockSize; /** * Threshold for big blocks. * * @var int */ public $bigBlockThreshold; /** * Reads an OLE container from the contents of the file given. * * @acces public * * @param string $filename * * @return bool true on success, PEAR_Error on failure */ public function read($filename) { $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 * * @return int */ public function getBlockOffset($blockId) { 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 * * @return int */ private static function readInt1($fileHandle) { [, $tmp] = unpack('c', fread($fileHandle, 1) ?: '') ?: [0, 0]; return $tmp; } /** * Reads an unsigned short (2 octets). * * @param resource $fileHandle file handle * * @return int */ private static function readInt2($fileHandle) { [, $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 * * @return int */ private static function readInt4($fileHandle) { [, $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($blockId) { $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 OLE\PPS\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)) { //* @phpstan-ignore-line 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($index) { 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($index) { 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($index) { 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() { 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($index, $position, $length) { // 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($index) { 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($ascii) { $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) { 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($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/OLERead.php000064400000024323152427537250016443 0ustar00data = (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. * * @param ?int $stream * * @return null|string */ public function getStream($stream) { 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($block) { $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. * * @param string $data * @param int $pos * * @return int */ private static function getInt4d($data, $pos) { 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/IntOrFloat.php000064400000000673152427537250017253 0ustar00lastModifiedBy = $this->creator; $this->created = self::intOrFloatTimestamp(null); $this->modified = $this->created; } /** * Get Creator. */ public function getCreator(): string { return $this->creator; } /** * Set Creator. * * @return $this */ public function setCreator(string $creator): self { $this->creator = $creator; return $this; } /** * Get Last Modified By. */ public function getLastModifiedBy(): string { return $this->lastModifiedBy; } /** * Set Last Modified By. * * @return $this */ public function setLastModifiedBy(string $modifiedBy): self { $this->lastModifiedBy = $modifiedBy; return $this; } /** * @param null|float|int|string $timestamp * * @return float|int */ private static function intOrFloatTimestamp($timestamp) { if ($timestamp === null) { $timestamp = (float) (new DateTime())->format('U'); } elseif (is_string($timestamp)) { if (is_numeric($timestamp)) { $timestamp = (float) $timestamp; } else { $timestamp = (string) preg_replace('/[.][0-9]*$/', '', $timestamp); $timestamp = (string) preg_replace('/^(\\d{4})- (\\d)/', '$1-0$2', $timestamp); $timestamp = (string) preg_replace('/^(\\d{4}-\\d{2})- (\\d)/', '$1-0$2', $timestamp); $timestamp = (float) (new DateTime($timestamp))->format('U'); } } return IntOrFloat::evaluate($timestamp); } /** * Get Created. * * @return float|int */ public function getCreated() { return $this->created; } /** * Set Created. * * @param null|float|int|string $timestamp * * @return $this */ public function setCreated($timestamp): self { $this->created = self::intOrFloatTimestamp($timestamp); return $this; } /** * Get Modified. * * @return float|int */ public function getModified() { return $this->modified; } /** * Set Modified. * * @param null|float|int|string $timestamp * * @return $this */ public function setModified($timestamp): self { $this->modified = self::intOrFloatTimestamp($timestamp); return $this; } /** * Get Title. */ public function getTitle(): string { return $this->title; } /** * Set Title. * * @return $this */ public function setTitle(string $title): self { $this->title = $title; return $this; } /** * Get Description. */ public function getDescription(): string { return $this->description; } /** * Set Description. * * @return $this */ public function setDescription(string $description): self { $this->description = $description; return $this; } /** * Get Subject. */ public function getSubject(): string { return $this->subject; } /** * Set Subject. * * @return $this */ public function setSubject(string $subject): self { $this->subject = $subject; return $this; } /** * Get Keywords. */ public function getKeywords(): string { return $this->keywords; } /** * Set Keywords. * * @return $this */ public function setKeywords(string $keywords): self { $this->keywords = $keywords; return $this; } /** * Get Category. */ public function getCategory(): string { return $this->category; } /** * Set Category. * * @return $this */ public function setCategory(string $category): self { $this->category = $category; return $this; } /** * Get Company. */ public function getCompany(): string { return $this->company; } /** * Set Company. * * @return $this */ public function setCompany(string $company): self { $this->company = $company; return $this; } /** * Get Manager. */ public function getManager(): string { return $this->manager; } /** * Set Manager. * * @return $this */ public function setManager(string $manager): self { $this->manager = $manager; return $this; } /** * Get a List of Custom Property Names. * * @return string[] */ public function getCustomProperties(): array { return array_keys($this->customProperties); } /** * Check if a Custom Property is defined. */ public function isCustomPropertySet(string $propertyName): bool { return array_key_exists($propertyName, $this->customProperties); } /** * Get a Custom Property Value. * * @return mixed */ public function getCustomPropertyValue(string $propertyName) { if (isset($this->customProperties[$propertyName])) { return $this->customProperties[$propertyName]['value']; } return null; } /** * Get a Custom Property Type. * * @return null|string */ public function getCustomPropertyType(string $propertyName) { return $this->customProperties[$propertyName]['type'] ?? null; } /** * @param mixed $propertyValue */ private function identifyPropertyType($propertyValue): string { if (is_float($propertyValue)) { return self::PROPERTY_TYPE_FLOAT; } if (is_int($propertyValue)) { return self::PROPERTY_TYPE_INTEGER; } if (is_bool($propertyValue)) { return self::PROPERTY_TYPE_BOOLEAN; } return self::PROPERTY_TYPE_STRING; } /** * Set a Custom Property. * * @param mixed $propertyValue * @param string $propertyType * 'i' : Integer * 'f' : Floating Point * 's' : String * 'd' : Date/Time * 'b' : Boolean * * @return $this */ public function setCustomProperty(string $propertyName, $propertyValue = '', $propertyType = null): self { if (($propertyType === null) || (!in_array($propertyType, self::VALID_PROPERTY_TYPE_LIST))) { $propertyType = $this->identifyPropertyType($propertyValue); } if (!is_object($propertyValue)) { $this->customProperties[$propertyName] = [ 'value' => self::convertProperty($propertyValue, $propertyType), 'type' => $propertyType, ]; } return $this; } private const PROPERTY_TYPE_ARRAY = [ 'i' => self::PROPERTY_TYPE_INTEGER, // Integer 'i1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Signed Integer 'i2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Signed Integer 'i4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Signed Integer 'i8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Signed Integer 'int' => self::PROPERTY_TYPE_INTEGER, // Integer 'ui1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Unsigned Integer 'ui2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Unsigned Integer 'ui4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Unsigned Integer 'ui8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Unsigned Integer 'uint' => self::PROPERTY_TYPE_INTEGER, // Unsigned Integer 'f' => self::PROPERTY_TYPE_FLOAT, // Real Number 'r4' => self::PROPERTY_TYPE_FLOAT, // 4-Byte Real Number 'r8' => self::PROPERTY_TYPE_FLOAT, // 8-Byte Real Number 'decimal' => self::PROPERTY_TYPE_FLOAT, // Decimal 's' => self::PROPERTY_TYPE_STRING, // String 'empty' => self::PROPERTY_TYPE_STRING, // Empty 'null' => self::PROPERTY_TYPE_STRING, // Null 'lpstr' => self::PROPERTY_TYPE_STRING, // LPSTR 'lpwstr' => self::PROPERTY_TYPE_STRING, // LPWSTR 'bstr' => self::PROPERTY_TYPE_STRING, // Basic String 'd' => self::PROPERTY_TYPE_DATE, // Date and Time 'date' => self::PROPERTY_TYPE_DATE, // Date and Time 'filetime' => self::PROPERTY_TYPE_DATE, // File Time 'b' => self::PROPERTY_TYPE_BOOLEAN, // Boolean 'bool' => self::PROPERTY_TYPE_BOOLEAN, // Boolean ]; private const SPECIAL_TYPES = [ 'empty' => '', 'null' => null, ]; /** * Convert property to form desired by Excel. * * @param mixed $propertyValue * * @return mixed */ public static function convertProperty($propertyValue, string $propertyType) { return self::SPECIAL_TYPES[$propertyType] ?? self::convertProperty2($propertyValue, $propertyType); } /** * Convert property to form desired by Excel. * * @param mixed $propertyValue * * @return mixed */ private static function convertProperty2($propertyValue, string $type) { $propertyType = self::convertPropertyType($type); switch ($propertyType) { case self::PROPERTY_TYPE_INTEGER: $intValue = (int) $propertyValue; return ($type[0] === 'u') ? abs($intValue) : $intValue; case self::PROPERTY_TYPE_FLOAT: return (float) $propertyValue; case self::PROPERTY_TYPE_DATE: return self::intOrFloatTimestamp($propertyValue); case self::PROPERTY_TYPE_BOOLEAN: return is_bool($propertyValue) ? $propertyValue : ($propertyValue === 'true'); default: // includes string return $propertyValue; } } public static function convertPropertyType(string $propertyType): string { return self::PROPERTY_TYPE_ARRAY[$propertyType] ?? self::PROPERTY_TYPE_UNKNOWN; } public function getHyperlinkBase(): string { return $this->hyperlinkBase; } public function setHyperlinkBase(string $hyperlinkBase): self { $this->hyperlinkBase = $hyperlinkBase; return $this; } } phpspreadsheet/src/PhpSpreadsheet/Document/Security.php000064400000006004152427537250017403 0ustar00lockRevision || $this->lockStructure || $this->lockWindows; } public function getLockRevision(): bool { return $this->lockRevision; } public function setLockRevision(?bool $locked): self { if ($locked !== null) { $this->lockRevision = $locked; } return $this; } public function getLockStructure(): bool { return $this->lockStructure; } public function setLockStructure(?bool $locked): self { if ($locked !== null) { $this->lockStructure = $locked; } return $this; } public function getLockWindows(): bool { return $this->lockWindows; } public function setLockWindows(?bool $locked): self { if ($locked !== null) { $this->lockWindows = $locked; } return $this; } public function getRevisionsPassword(): string { return $this->revisionsPassword; } /** * Set RevisionsPassword. * * @param string $password * @param bool $alreadyHashed If the password has already been hashed, set this to true * * @return $this */ public function setRevisionsPassword(?string $password, bool $alreadyHashed = false) { if ($password !== null) { if (!$alreadyHashed) { $password = PasswordHasher::hashPassword($password); } $this->revisionsPassword = $password; } return $this; } public function getWorkbookPassword(): string { return $this->workbookPassword; } /** * Set WorkbookPassword. * * @param string $password * @param bool $alreadyHashed If the password has already been hashed, set this to true * * @return $this */ public function setWorkbookPassword(?string $password, bool $alreadyHashed = false) { if ($password !== null) { if (!$alreadyHashed) { $password = PasswordHasher::hashPassword($password); } $this->workbookPassword = $password; } return $this; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php000064400000016343152427537250022320 0ustar00getMessage(); } $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. * * @return mixed */ private static function incrementing(float $startDate, int $endDays, array $holidayArray) { // 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. * * @return mixed */ private static function decrementing(float $startDate, int $endDays, array $holidayArray) { // 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/DateValue.php000064400000015262152427537250022611 0ustar00 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 ((strpos((string) $t, ':') === false)) ? 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 mixed 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) { $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/Constants.php000064400000002117152427537250022706 0ustar00 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/Time.php000064400000012013152427537250021624 0ustar00getMessage(); } 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($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/Days360.php000064400000012245152427537250022066 0ustar00getMessage(); } 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/Helpers.php000064400000022115152427537250022334 0ustar00format('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. * * @param mixed $value * @param mixed $altValue */ public static function replaceIfEmpty(&$value, $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. * * @return mixed */ public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false) { $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) : (float) $excelDateValue; } // RETURNDATE_UNIX_TIMESTAMP) return (int) SharedDateHelper::excelToTimestamp($excelDateValue); } /** * Return result in one of three formats. * * @return mixed */ public static function returnIn3FormatsFloat(float $excelDateValue) { $retType = Functions::getReturnDateType(); if ($retType === Functions::RETURNDATE_EXCEL) { return $excelDateValue; } if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { return (int) SharedDateHelper::excelToTimestamp($excelDateValue); } // RETURNDATE_PHP_DATETIME_OBJECT return SharedDateHelper::excelToDateTimeObject($excelDateValue); } /** * Return result in one of three formats. * * @return mixed */ public static function returnIn3FormatsObject(DateTime $PHPDateObject) { $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 (int) 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. * * @param mixed $number */ public static function nullFalseTrueToNumber(&$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. * * @param mixed $number * * @return float|int */ public static function validateNumericNull($number) { $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. * * @param mixed $number * * @return float */ public static function validateNotNegative($number) { 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($dateArray): array { return is_array($dateArray) ? $dateArray : ['error_count' => 1]; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php000064400000016470152427537250021616 0ustar00getMessage(); } // Execute function $excelDateValue = SharedDateHelper::formattedPHPToExcel(/** @scrutinizer ignore-type */ $year, $month, $day); return Helpers::returnIn3FormatsFloat($excelDateValue); } /** * Convert year from multiple formats to int. * * @param mixed $year */ private static function getYear($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. * * @param mixed $month */ private static function getMonth($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. * * @param mixed $day */ private static function getDay($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/Current.php000064400000004335152427537250022360 0ustar00format('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 mixed 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() { $dti = new DateTimeImmutable(); $dateArray = Helpers::dateParse($dti->format('c')); return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : ExcelError::VALUE(); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php000064400000024076152427537250021635 0ustar00getMessage(); } // 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($dateValue) { 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|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($dateValue, $style = 1) { 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($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($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. * * @param mixed $dateValue */ private static function validateDateValue($dateValue): float { if (is_bool($dateValue)) { throw new Exception(ExcelError::VALUE()); } return Helpers::getDateValue($dateValue); } /** * Validate method parameter. * * @param mixed $method */ private static function validateMethod($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/TimeValue.php000064400000006764152427537250022641 0ustar00 24) { $arraySplit[0] = ($arraySplit[0] % 24); $timeValue = implode(':', $arraySplit); } $PHPDateArray = Helpers::dateParse($timeValue); $retValue = ExcelError::VALUE(); if (Helpers::dateParseSucceeded($PHPDateArray)) { /** @var int */ $hour = $PHPDateArray['hour']; /** @var int */ $minute = $PHPDateArray['minute']; /** @var int */ $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/NetworkDays.php000064400000010301152427537250023176 0ustar00getMessage(); } // 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/Month.php000064400000010763152427537250022025 0ustar00getMessage(); } $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|mixed 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($dateValue, $adjustmentMonths) { 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/DateTimeExcel/DateParts.php000064400000012125152427537250022621 0ustar00= 0) { return $weirdResult; } try { $dateValue = Helpers::getDateValue($dateValue); } catch (Exception $e) { return $e->getMessage(); } // Execute function $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); 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($dateValue) { 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); 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($dateValue) { 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); 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($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/YearFrac.php000064400000013310152427537250022423 0ustar00getMessage(); } switch ($method) { case 0: return Functions::scalar(Days360::between($startDate, $endDate)) / 360; case 1: return self::method1($startDate, $endDate); case 2: return Functions::scalar(Difference::interval($startDate, $endDate)) / 360; case 3: return Functions::scalar(Difference::interval($startDate, $endDate)) / 365; case 4: return Functions::scalar(Days360::between($startDate, $endDate, true)) / 360; } return ExcelError::NAN(); } /** * Excel 1900 calendar treats date argument of null as 1900-01-00. Really. * * @param mixed $startDate * @param mixed $endDate */ private static function excelBug(float $sDate, $startDate, $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/Difference.php000064400000013727152427537250022775 0ustar00getMessage(); } // 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. * * @param bool|int $retVal * * @return null|bool|int */ private static function replaceRetValue($retVal, string $unit, string $compare) { 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/TimeParts.php000064400000010555152427537250022647 0ustar00getMessage(); } // Execute function $timeValue = fmod($timeValue, 1); $timeValue = SharedDateHelper::excelToDateTimeObject($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($timeValue) { 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); 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($timeValue) { 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); return (int) $timeValue->format('s'); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php000064400000004325152427537250021635 0ustar00getMessage(); } // 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/Logical/Operations.php000064400000015047152427537250021760 0ustar00 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(...$args) { return self::countTrueValues($args, function (int $trueValueCount): bool { return $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($logical = false) { 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; } /** * @return bool|string */ private static function countTrueValues(array $args, callable $func) { $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/Logical/Boolean.php000064400000001035152427537250021204 0ustar00 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($testValue = '', $errorpart = '') { 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($testValue = '', $napart = '') { 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(...$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/Information/Value.php000064400000023606152427537250021624 0ustar00getCoordinate()) { 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($value = null) { 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($value = null) { 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($value = null) { 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($value = null) { 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($value = null) { 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($value = null) { 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) * * @return array|bool|string */ public static function isFormula($cellReference = '', ?Cell $cell = null) { if ($cell === null) { return ExcelError::REF(); } $fullCellReference = Functions::expandDefinedName((string) $cellReference, $cell); if (strpos($cellReference, '!') !== false) { $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 ((strlen($value) > 0) && ($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 number 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) { $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 ((strlen($value) > 0) && ($value[0] == '#')) { return 16; } return 2; } return 0; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ExcelError.php000064400000006756152427537250022631 0ustar00 */ 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 ]; /** * List of error codes. Replaced by constant; * previously it was public and updateable, allowing * user to make inappropriate alterations. * * @deprecated 1.25.0 Use ERROR_CODES constant instead. * * @var array */ public static $errorCodes = self::ERROR_CODES; /** * @param mixed $value */ public static function throwError($value): string { return in_array($value, self::ERROR_CODES, true) ? $value : self::ERROR_CODES['value']; } /** * ERROR_TYPE. * * @param mixed $value Value to check * * @return array|int|string */ public static function type($value = '') { 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.php000064400000003654152427537250022637 0ustar00getMessage(); } // 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 = null; switch ($matchType) { case self::MATCHTYPE_LARGEST_VALUE: $valueKey = self::matchLargestValue($lookupArray, $lookupValue, $keySet); break; case self::MATCHTYPE_FIRST_VALUE: $valueKey = self::matchFirstValue($lookupArray, $lookupValue); break; case self::MATCHTYPE_SMALLEST_VALUE: default: $valueKey = self::matchSmallestValue($lookupArray, $lookupValue); } if ($valueKey !== null) { return ++$valueKey; } // Unsuccessful in finding a match, return #N/A error value return ExcelError::NA(); } /** * @param mixed $lookupValue * * @return mixed */ private static function matchFirstValue(array $lookupArray, $lookupValue) { 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; } /** * @param mixed $lookupValue * * @return mixed */ private static function matchLargestValue(array $lookupArray, $lookupValue, array $keySet) { 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; } /** * @param mixed $lookupValue * * @return mixed */ private static function matchSmallestValue(array $lookupArray, $lookupValue) { $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; } /** * @param mixed $lookupValue */ private static function validateLookupValue($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()); } } /** * @param mixed $matchType */ private static function validateMatchType($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()); } } /** * @param mixed $matchType */ private static function prepareLookupArray(array $lookupArray, $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/Unique.php000064400000010513152427537250021430 0ustar00 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( function (string $value) { return 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, function ($value) { return $value === 1; } ); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php000064400000010536152427537250021550 0ustar00getMessage(); } $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($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/Indirect.php000064400000011723152427537250021727 0ustar00getCoordinate()); 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 $e) { 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 mixed 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) { 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/Sort.php000064400000030173152427537250021115 0ustar00getMessage(); } // 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($sortArray, ...$args) { 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); } /** * @param mixed $sortIndex * @param mixed $sortOrder */ private static function validateScalarArgumentsForSort(&$sortIndex, &$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); } /** * @param mixed $sortVector */ private static function validateSortVector($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; } /** * @param mixed $sortOrder */ private static function validateSortOrder($sortOrder): int { $sortOrder = self::validateInt($sortOrder); if (($sortOrder == self::ORDER_ASCENDING || $sortOrder === self::ORDER_DESCENDING) === false) { throw new Exception(ExcelError::VALUE()); } return $sortOrder; } /** * @param array $sortIndex * @param mixed $sortOrder */ private static function validateArrayArgumentsForSort(&$sortIndex, &$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, $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; } $sortArguments = self::applyPHP7Patch($sortArray, $sortArguments); $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; } $sortArguments = self::applyPHP7Patch($sortArray, $sortArguments); $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; } /** * Hack to handle PHP 7: * From PHP 8.0.0, If two members compare as equal in a sort, they retain their original order; * but prior to PHP 8.0.0, their relative order in the sorted array was undefined. * MS Excel replicates the PHP 8.0.0 behaviour, retaining the original order of matching elements. * To replicate that behaviour with PHP 7, we add an extra sort based on the row index. */ private static function applyPHP7Patch(array $sortArray, array $sortArguments): array { if (PHP_VERSION_ID < 80000) { $sortArguments[] = range(1, count($sortArray)); $sortArguments[] = SORT_ASC; } return $sortArguments; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php000064400000005367152427537250021577 0ustar00getWorkSheet(); $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 (strpos($cellAddress, ':') !== false) { [$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 (strpos($cellAddress, '!') !== false) { [$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/VLookup.php000064400000010112152427537250021554 0ustar00getMessage(); } $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 = [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($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/Filter.php000064400000004122152427537250021406 0ustar00getWorksheet()->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/LookupRefValidations.php000064400000001722152427537250024270 0ustar00getColumn()) : 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) { 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 (int) 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); if (strpos($cellAddress, ':') !== false) { [$startAddress, $endAddress] = explode(':', $cellAddress); $startAddress = (string) preg_replace('/[^a-z]/i', '', $startAddress); $endAddress = (string) preg_replace('/[^a-z]/i', '', $endAddress); return range( (int) Coordinate::columnIndexFromString($startAddress), (int) Coordinate::columnIndexFromString($endAddress) ); } $cellAddress = (string) preg_replace('/[^a-z]/i', '', $cellAddress); return (int) 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[]|string */ public static function ROW($cellAddress = null, ?Cell $cell = null) { 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); if (strpos($cellAddress, ':') !== false) { [$startAddress, $endAddress] = explode(':', $cellAddress); $startAddress = (string) preg_replace('/\D/', '', $startAddress); $endAddress = (string) preg_replace('/\D/', '', $endAddress); return array_map( function ($value) { return [$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/Lookup.php000064400000006755152427537250021450 0ustar00 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) { $lookupVector = LookupRef\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 = LookupRef\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/LookupBase.php000064400000004366152427537250022237 0ustar00getParent() : null) ->extractCellRange($cellAddress, $worksheet, false); } private static function extractWorksheet(?string $cellAddress, Cell $cell): array { $cellAddress = self::assessCellAddress($cellAddress ?? '', $cell); $sheetName = ''; if (strpos($cellAddress, '!') !== false) { [$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; } /** * @param mixed $width * @param mixed $columns */ private static function adjustEndCellColumnForWidth(string $endCellColumn, $width, int $startCellColumn, $columns): int { $endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1; if (($width !== null) && (!is_object($width))) { $endCellColumn = $startCellColumn + (int) $width - 1; } else { $endCellColumn += (int) $columns; } return $endCellColumn; } /** * @param mixed $height * @param mixed $rows * @param mixed $endCellRow */ private static function adustEndCellRowForHeight($height, int $startCellRow, $rows, $endCellRow): int { if (($height !== null) && (!is_object($height))) { $endCellRow = $startCellRow + (int) $height - 1; } else { $endCellRow += (int) $rows; } return $endCellRow; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php000064400000002527152427537250022135 0ustar00getHyperlink()->setUrl($linkURL); $cell->getHyperlink()->setTooltip($displayName); return $displayName; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Address.php000064400000011301152427537250021543 0ustar00 '') { if (strpos($sheetName, ' ') !== false || strpos($sheetName, '[') !== false) { $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/Matrix.php000064400000010507152427537250021431 0ustar00 1 && (count($values, COUNT_NORMAL) === 1 || count($values, COUNT_RECURSIVE) === count($values, COUNT_NORMAL)); } /** * TRANSPOSE. * * @param array|mixed $matrixData A matrix of values * * @return array */ public static function transpose($matrixData) { $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($matrix, $rowNum = 0, $columnNum = null) { 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( function ($value) { return [$value]; }, array_column($matrix, $columnNum) ); } $rowNum = $rowKeys[--$rowNum]; return $matrix[$rowNum][$columnNum]; } /** @return mixed */ private static function extractRowValue(array $matrix, array $rowKeys, int $rowNum) { 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/Selection.php000064400000002743152427537250022115 0ustar00 $entryCount)) { return ExcelError::VALUE(); } if (is_array($chooseArgs[$chosenEntry])) { return Functions::flattenArray($chooseArgs[$chosenEntry]); } return $chooseArgs[$chosenEntry]; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php000064400000000307152427537250022072 0ustar00 1) { return ExcelError::NAN(); } $row = array_pop($columnData); return array_pop($row); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCount.php000064400000004336152427537250021162 0ustar00= 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( function ($rowValue) { return (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : ($rowValue[0] ?? ''); }, $baseQuery ); return (count($rowQuery) > 1) ? 'OR(' . implode(',', $rowQuery) . ')' : ($rowQuery[0] ?? ''); } /** * @param mixed $criterion */ private static function buildCondition($criterion, string $criterionName): string { $ifCondition = Functions::ifCondition($criterion); // Check for wildcard characters used in the condition $result = preg_match('/(?[^"]*)(?".*[*?].*")/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; } /** * @return mixed */ private static function processCondition(string $criterion, array $fields, array $dataValues, string $conditions) { $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) && strpos($dataValue, '"') !== false) { $dataValue = str_replace('"', '""', $dataValue); } $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue; } return str_replace('[:' . $criterion . ']', $dataValue, $conditions); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCountA.php000064400000004240152427537250021255 0ustar00value = $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 (strpos($srStringRemainder, self::OPEN_BRACE) !== false) { ++$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(); return $cellRange; } private function isRowReference(): bool { return strpos($this->value, '[@') !== false || strpos($this->value, '[' . self::ITEM_SPECIFIER_THIS_ROW . ']') !== false; } /** * @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]; $this->columns = $this->getColumns($cell, $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) { 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); } /** @var string $reference */ 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 { switch ($reference) { case self::ITEM_SPECIFIER_ALL: case self::ITEM_SPECIFIER_HEADERS: return $this->headersRow ?? $this->firstDataRow; case self::ITEM_SPECIFIER_DATA: return $this->firstDataRow; case self::ITEM_SPECIFIER_TOTALS: return $this->totalsRow ?? $this->lastDataRow; } return $this->headersRow ?? $this->firstDataRow; } private function getMaximumRow(string $reference): int { switch ($reference) { case self::ITEM_SPECIFIER_HEADERS: return $this->headersRow ?? $this->firstDataRow; case self::ITEM_SPECIFIER_DATA: return $this->lastDataRow; case self::ITEM_SPECIFIER_ALL: case self::ITEM_SPECIFIER_TOTALS: return $this->totalsRow ?? $this->lastDataRow; } return $this->totalsRow ?? $this->lastDataRow; } public function value(): string { return $this->value; } /** * @return array */ private function getRowsForColumnReference(string &$reference, int $startRow, int $endRow): array { $rowsSelected = false; foreach (self::ITEM_SPECIFIER_ROWS_SET as $rowReference) { $pattern = '/\[' . $rowReference . '\]/mui'; /** @var string $reference */ 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; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php000064400000012325152427537250023405 0ustar00indexStart = (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( function ($argument) { return is_countable($argument) ? count($argument) : 1; }, $arguments ); } private function columns(array $arguments): array { return array_map( function ($argument) { 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, function ($value) { return $value > 1; } ); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php000064400000006537152427537250020713 0ustar00cellStack = $stack; } /** * Enable/Disable Calculation engine logging. * * @param bool $writeDebugLog */ public function setWriteDebugLog($writeDebugLog): void { $this->writeDebugLog = $writeDebugLog; } /** * Return whether calculation engine logging is enabled or disabled. * * @return bool */ public function getWriteDebugLog() { return $this->writeDebugLog; } /** * Enable/Disable echoing of debug log information. * * @param bool $echoDebugLog */ public function setEchoDebugLog($echoDebugLog): void { $this->echoDebugLog = $echoDebugLog; } /** * Return whether echoing of debug log information is enabled or disabled. * * @return bool */ public function getEchoDebugLog() { return $this->echoDebugLog; } /** * Write an entry to the calculation engine debug log. * * @param mixed $args */ public function writeDebugLog(string $message, ...$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() { return $this->debugLog; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php000064400000013565152427537250022571 0ustar00[-+])? *\% *(?[-+])? *(?[0-9]+\.?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?[-+])? *(?[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 '~^(?:(?: *(?[-+])? *(?[' . $currencyCodes . ']) *(?[-+])? *(?[0-9]+[' . $decimalSeparator . ']?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?[-+])? *(?[0-9]+' . $decimalSeparator . '?[0-9]*(?:E[-+]?[0-9]*)?) *(?[' . $currencyCodes . ']) *))$~ui'; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/BranchPruner.php000064400000014252152427537250022056 0ustar00branchPruningEnabled = $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'); } } } /** * @param mixed $value */ public function closingBrace($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/ArrayArgumentProcessor.php000064400000014605152427537250024150 0ustar00hasArrayArgument() === 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!']; } /** * @param mixed ...$arguments */ private static function evaluateVectorMatrixPair(callable $method, array $matrixIndexes, ...$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; } /** * @param mixed ...$arguments */ private static function evaluateMatrixPair(callable $method, array $matrixIndexes, ...$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; } /** * @param mixed ...$arguments */ private static function evaluateVectorPair(callable $method, int $rowIndex, int $columnIndex, ...$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. * * @param mixed ...$arguments */ private static function evaluateNthArgumentAsArray(callable $method, int $nthArgument, ...$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/CyclicReferenceStack.php000064400000002350152427537250023474 0ustar00stack); } /** * Push a new entry onto the stack. * * @param mixed $value */ public function push($value): void { $this->stack[$value] = $value; } /** * Pop the last entry from the stack. * * @return mixed */ public function pop() { return array_pop($this->stack); } /** * Test to see if a specified entry exists on the stack. * * @param mixed $value The value to test * * @return bool */ public function onStack($value) { 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() { return $this->stack; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Token/Stack.php000064400000005752152427537250020412 0ustar00branchPruner = $branchPruner; } /** * Return the number of entries on the stack. */ public function count(): int { return $this->count; } /** * Push a new entry onto the stack. * * @param mixed $value */ public function push(string $type, $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; } /** * @param mixed $value */ public function getStackItem(string $type, $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/MathTrig/Trig/Cotangent.php000064400000006744152427537250022635 0ustar00getMessage(); } 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) { 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/Secant.php000064400000003505152427537250022120 0ustar00getMessage(); } 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/Trig/Tangent.php000064400000012667152427537250022314 0ustar00getMessage(); } 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($angle) { 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($xCoordinate, $yCoordinate) { 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/Cosecant.php000064400000003517152427537250022445 0ustar00getMessage(); } 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/Cosine.php000064400000006606152427537250022130 0ustar00getMessage(); } 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($number) { 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/Sine.php000064400000006522152427537250021603 0ustar00getMessage(); } 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($angle) { 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/Combinations.php000064400000007054152427537250022426 0ustar00getMessage(); } return round(Factorial::fact($numObjs) / Factorial::fact($numObjs - $numInSet)) / Factorial::fact($numInSet); // @phpstan-ignore-line } /** * 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($numObjs, $numInSet) { 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(); } return round( Factorial::fact($numObjs + $numInSet - 1) / Factorial::fact($numObjs - 1) // @phpstan-ignore-line ) / Factorial::fact($numInSet); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php000064400000001743152427537250020534 0ustar00getMessage(); } return exp($number); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php000064400000002100152427537250021504 0ustar00getMessage(); } return (int) floor($number); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php000064400000007642152427537250022114 0ustar00getMessage(); } 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. * * @param mixed $item */ private static function numericNotString($item): bool { return is_numeric($item) && !is_string($item); } /** * SUMX2MY2. * * @param mixed[] $matrixData1 Matrix #1 * @param mixed[] $matrixData2 Matrix #2 * * @return float|string */ public static function sumXSquaredMinusYSquared($matrixData1, $matrixData2) { 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 * * @return float|string */ public static function sumXSquaredPlusYSquared($matrixData1, $matrixData2) { 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 * * @return float|string */ public static function sumXMinusYSquared($matrixData1, $matrixData2) { 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/Logarithms.php000064400000006254152427537250022113 0ustar00getMessage(); } 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($number) { 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($number) { 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.php000064400000003533152427537250020730 0ustar00getMessage(); } 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) { 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/Operations.php000064400000011604152427537250022120 0ustar00getMessage(); } 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 $x * Or can be an array of values * @param array|float|int $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($x, $y) { 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 * * @return float|string */ public static function product(...$args) { $args = array_filter( Functions::flattenArray($args), function ($value) { return $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($numerator, $denominator) { 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/Helpers.php000064400000006206152427537250021401 0ustar00= 0. * * @param float|int $number */ public static function validateNotNegative($number, ?string $except = null): void { if ($number >= 0) { return; } throw new Exception($except ?? ExcelError::NAN()); } /** * Confirm number > 0. * * @param float|int $number */ public static function validatePositive($number, ?string $except = null): void { if ($number > 0) { return; } throw new Exception($except ?? ExcelError::NAN()); } /** * Confirm number != 0. * * @param float|int $number */ public static function validateNotZero($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. * * @param float $result Number * * @return float|string */ public static function numberOrNan($result) { return is_nan($result) ? ExcelError::NAN() : $result; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Round.php000064400000017502152427537250021067 0ustar00getMessage(); } 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) { 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 (PHP_VERSION_ID >= 80400) { return round( (float) (string) $number, $digits, RoundingMode::AwayFromZero //* @phpstan-ignore-line ); } if ($number < 0.0) { return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); } return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); } /** * ROUNDDOWN. * * Rounds a number down to a specified number of decimal places * * @param null|array|float|string $number Number to round, or can be an array of numbers * @param array|float|int|string $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) { 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 (PHP_VERSION_ID >= 80400) { return round( (float) (string) $number, $digits, RoundingMode::TowardsZero //* @phpstan-ignore-line ); } if ($number < 0.0) { return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); } return round($number - 0.5 * 0.1 ** $digits, $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($number, $multiple) { 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) { 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) { 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/Base.php000064400000004362152427537250020652 0ustar00getMessage(); } return self::calculate($number, $radix, $minLength); } /** * @param mixed $minLength */ private static function calculate(float $number, int $radix, $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/Ceiling.php000064400000013725152427537250021355 0ustar00getMessage(); } 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($number, $significance = null, $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(); } 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($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(); } 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 ((float) $significance < 0) || ((float) $number < 0 && !empty($mode)); } /** * Avoid Scrutinizer problems concerning complexity. * * @return float|string */ private static function argumentsOk(float $number, float $significance) { 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/SeriesSum.php000064400000003113152427537250021710 0ustar00getMessage(); } return $returnValue; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php000064400000012271152427537250023133 0ustar00getMessage(); } 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($matrixValues) { try { $matrix = self::getMatrix($matrixValues); return $matrix->determinant(); } catch (MatrixException $ex) { 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($matrixValues) { try { $matrix = self::getMatrix($matrixValues); return $matrix->inverse()->toArray(); } catch (MatrixDiv0Exception $e) { return ExcelError::NAN(); } catch (MatrixException $e) { 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($matrixData1, $matrixData2) { try { $matrixA = self::getMatrix($matrixData1); $matrixB = self::getMatrix($matrixData2); return $matrixA->multiply($matrixB)->toArray(); } catch (MatrixException $ex) { 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($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/Subtotal.php000064400000011213152427537250021566 0ustar00getWorksheet()->getRowDimension($row)->getVisible(); }, ARRAY_FILTER_USE_KEY ); } /** * @param mixed $cellReference * @param mixed $args */ protected static function filterFormulaArgs($cellReference, $args): array { return array_filter( $args, function ($index) use ($cellReference) { $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 ); } 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 * * @return float|string */ public static function evaluate($functionType, ...$args) { $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)) { /** @var callable */ $call = self::CALL_FUNCTIONS[$subtotal]; return call_user_func_array($call, $aArgs); } return ExcelError::VALUE(); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php000064400000064117152427537250021060 0ustar00 ['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($aValue, $style = 0) { 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.php000064400000001754152427537250021560 0ustar00getMessage(); } return abs($number); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php000064400000002145152427537250020675 0ustar00getMessage(); } return Helpers::returnSign($number); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php000064400000015050152427537250021055 0ustar00getMessage(); } 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($number, $significance = null, $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. * * @return float|string */ private static function argumentsOkPrecise(float $number, float $significance) { 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) { 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. * * @return float|string */ private static function argumentsOk(float $number, float $significance) { 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/Factorial.php000064400000007351152427537250021705 0ustar00getMessage(); } $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) { 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|string The result, or a string containing an error */ public static function multinomial(...$args) { $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/Lcm.php000064400000007122152427537250020510 0ustar00 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(...$args) { 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/MathTrig/Gcd.php000064400000003754152427537250020501 0ustar00getMessage(); } 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/Trunc.php000064400000002323152427537250021066 0ustar00getMessage(); } 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($rows = 1, $columns = 1, $min = 0, $max = 1, $wholeNumber = false) { 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) { 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.php000064400000005641152427537250021162 0ustar00 1000, 'D' => 500, 'C' => 100, 'L' => 50, 'X' => 10, 'V' => 5, 'I' => 1, ]; /** * Recursively calculate the arabic value of a roman numeral. * * @param int $sum * @param int $subtract * * @return int */ private static function calculateArabic(array $roman, &$sum = 0, $subtract = 0) { $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; } /** * @param mixed $value */ private static function mollifyScrutinizer($value): array { return is_array($value) ? $value : []; } private static function strSplit(string $roman): array { $rslt = str_split($roman); return self::mollifyScrutinizer($rslt); } /** * ARABIC. * * Converts a Roman numeral to an Arabic numeral. * * Excel Function: * ARABIC(text) * * @param mixed $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($roman) { 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(self::strSplit($roman)); } catch (Exception $e) { return ExcelError::VALUE(); // Invalid character detected } if ($negativeNumber) { $arabic *= -1; // The number should be negative } return $arabic; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Angle.php000064400000003421152427537250021021 0ustar00getMessage(); } 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($number) { 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/Sum.php000064400000006616152427537250020550 0ustar00 $arg) { // Is it a numeric value? if (is_numeric($arg) || empty($arg)) { if (is_string($arg)) { $arg = (int) $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|string The result, or a string containing an error */ public static function product(...$args) { $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/TextData/CharacterConvert.php000064400000005051152427537250023230 0ustar00 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($characters) { 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/Helpers.php000064400000005031152427537250021373 0ustar00 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($delimiter = '', $ignoreEmpty = true, ...$args) { 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($stringValue, $repeatCount) { 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/Replace.php000064400000011463152427537250021352 0ustar00getMessage(); } $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($text = '', $fromText = '', $toText = '', $instance = null) { 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/Trim.php000064400000003036152427537250020707 0ustar00 1) { $quotedDelimiters = array_map( function ($delimiter) { return preg_quote($delimiter ?? '', '/'); }, $valueSet ); $delimiters = implode('|', $quotedDelimiters); return '(' . $delimiters . ')'; } return '(' . preg_quote(/** @scrutinizer ignore-type */ 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; } /** * @param mixed $cellValue */ private static function formatValueMode0($cellValue): string { if (is_bool($cellValue)) { return Calculation::getLocaleBoolean($cellValue ? 'TRUE' : 'FALSE'); } return (string) $cellValue; } /** * @param mixed $cellValue */ private static function formatValueMode1($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/Search.php000064400000007055152427537250021206 0ustar00getMessage(); } 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($needle, $haystack, $offset = 1) { 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/CaseConvert.php000064400000004740152427537250022213 0ustar00getMessage(); } 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($value, $start, $chars) { 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($value, $chars = 1) { 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 mixed|mixed[] 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($text, $delimiter, $instance = 1, $matchMode = 0, $matchEnd = 0, $ifNotFound = '#N/A') { 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 mixed|mixed[] 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($text, $delimiter, $instance = 1, $matchMode = 0, $matchEnd = 0, $ifNotFound = '#N/A') { 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); } /** * @param null|array|string $delimiter * @param int $matchMode * @param int $matchEnd * @param mixed $ifNotFound * * @return array|string */ private static function validateTextBeforeAfter(string $text, $delimiter, int $instance, $matchMode, $matchEnd, $ifNotFound) { $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( function ($delimiter) { return 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.php000064400000026770152427537250021236 0ustar00getMessage(); } $mask = '$#,##0'; if ($decimals > 0) { $mask .= '.' . str_repeat('0', $decimals); } else { $round = 10 ** abs($decimals); if ($value < 0) { $round = 0 - $round; } $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($value, $decimals = 2, $noCommas = false) { 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($value, $format) { if (is_array($value) || is_array($format)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format); } $value = Helpers::extractString($value); $format = Helpers::extractString($format); if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) { $value = DateTimeExcel\DateValue::fromString($value) + DateTimeExcel\TimeValue::fromString($value); } return (string) NumberFormat::toFormattedString($value, $format); } /** * @param mixed $value Value to check * * @return mixed */ private static function convertValue($value, bool $spacesMeanZero = false) { $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($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 (strpos($value, ':') !== false) { $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 * @param mixed $format * * @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($value, $format = false) { 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; } /** * @param mixed $decimalSeparator */ private static function getDecimalSeparator($decimalSeparator): string { return empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : (string) $decimalSeparator; } /** * @param mixed $groupSeparator */ private static function getGroupSeparator($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 * * @return array|float|string */ public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null) { 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/Engineering/Constants.php000064400000000247152427537250022465 0ustar00getMessage(); } 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 { // special cases switch ($ord) { case 0: return self::besselY0($x); case 1: return self::besselY1($x); } return 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/ConvertUOM.php000064400000105716152427537250022521 0ustar00 ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Gram', 'AllowPrefix' => true], 'sg' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Slug', 'AllowPrefix' => false], 'lbm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Pound mass (avoirdupois)', 'AllowPrefix' => false], 'u' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U (atomic mass unit)', 'AllowPrefix' => true], 'ozm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => false], 'grain' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Grain', 'AllowPrefix' => false], 'cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false], 'shweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false], 'uk_cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], 'lcwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], 'hweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], 'stone' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Stone', 'AllowPrefix' => false], 'ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ton', 'AllowPrefix' => false], 'uk_ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], 'LTON' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], 'brton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], // Distance 'm' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Meter', 'AllowPrefix' => true], 'mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Statute mile', 'AllowPrefix' => false], 'Nmi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Nautical mile', 'AllowPrefix' => false], 'in' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Inch', 'AllowPrefix' => false], 'ft' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Foot', 'AllowPrefix' => false], 'yd' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Yard', 'AllowPrefix' => false], 'ang' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Angstrom', 'AllowPrefix' => true], 'ell' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Ell', 'AllowPrefix' => false], 'ly' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Light Year', 'AllowPrefix' => false], 'parsec' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false], 'pc' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false], 'Pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false], 'Picapt' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false], 'pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/6 in)', 'AllowPrefix' => false], 'survey_mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'U.S survey mile (statute mile)', 'AllowPrefix' => false], // Time 'yr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Year', 'AllowPrefix' => false], 'day' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false], 'd' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false], 'hr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Hour', 'AllowPrefix' => false], 'mn' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false], 'min' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false], 'sec' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true], 's' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true], // Pressure 'Pa' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true], 'p' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true], 'atm' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], 'at' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], 'mmHg' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'mm of Mercury', 'AllowPrefix' => true], 'psi' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'PSI', 'AllowPrefix' => true], 'Torr' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Torr', 'AllowPrefix' => true], // Force 'N' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Newton', 'AllowPrefix' => true], 'dyn' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true], 'dy' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true], 'lbf' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pound force', 'AllowPrefix' => false], 'pond' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pond', 'AllowPrefix' => true], // Energy 'J' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Joule', 'AllowPrefix' => true], 'e' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Erg', 'AllowPrefix' => true], 'c' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Thermodynamic calorie', 'AllowPrefix' => true], 'cal' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'IT calorie', 'AllowPrefix' => true], 'eV' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], 'ev' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], 'HPh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], 'hh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], 'Wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], 'wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], 'flb' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Foot-pound', 'AllowPrefix' => false], 'BTU' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false], 'btu' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false], // Power 'HP' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], 'h' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], 'W' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true], 'w' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true], 'PS' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Pferdestärke', 'AllowPrefix' => false], // Magnetism 'T' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Tesla', 'AllowPrefix' => true], 'ga' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Gauss', 'AllowPrefix' => true], // Temperature 'C' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false], 'cel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false], 'F' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false], 'fah' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false], 'K' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], 'kel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], 'Rank' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Rankine', 'AllowPrefix' => false], 'Reau' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Réaumur', 'AllowPrefix' => false], // Volume 'l' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], 'L' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], 'lt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], 'tsp' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Teaspoon', 'AllowPrefix' => false], 'tspm' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Modern Teaspoon', 'AllowPrefix' => false], 'tbs' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Tablespoon', 'AllowPrefix' => false], 'oz' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Fluid Ounce', 'AllowPrefix' => false], 'cup' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cup', 'AllowPrefix' => false], 'pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], 'us_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], 'uk_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.K. Pint', 'AllowPrefix' => false], 'qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Quart', 'AllowPrefix' => false], 'uk_qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Quart (UK)', 'AllowPrefix' => false], 'gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gallon', 'AllowPrefix' => false], 'uk_gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Gallon (UK)', 'AllowPrefix' => false], 'ang3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true], 'ang^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true], 'barrel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Oil Barrel', 'AllowPrefix' => false], 'bushel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Bushel', 'AllowPrefix' => false], 'in3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false], 'in^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false], 'ft3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false], 'ft^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false], 'ly3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false], 'ly^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false], 'm3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true], 'm^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true], 'mi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false], 'mi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false], 'yd3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false], 'yd^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false], 'Nmi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false], 'Nmi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false], 'Pica3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], 'Pica^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], 'Picapt3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], 'Picapt^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], 'GRT' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false], 'regton' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false], 'MTON' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Measurement Ton (Freight Ton)', 'AllowPrefix' => false], // Area 'ha' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Hectare', 'AllowPrefix' => true], 'uk_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'International Acre', 'AllowPrefix' => false], 'us_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'US Survey/Statute Acre', 'AllowPrefix' => false], 'ang2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true], 'ang^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true], 'ar' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Are', 'AllowPrefix' => true], 'ft2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false], 'ft^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false], 'in2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false], 'in^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false], 'ly2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false], 'ly^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false], 'm2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true], 'm^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true], 'Morgen' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Morgen', 'AllowPrefix' => false], 'mi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false], 'mi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false], 'Nmi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false], 'Nmi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false], 'Pica2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], 'Pica^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], 'Picapt2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], 'Picapt^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], 'yd2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false], 'yd^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false], // Information 'byte' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Byte', 'AllowPrefix' => true], 'bit' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Bit', 'AllowPrefix' => true], // Speed 'm/s' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true], 'm/sec' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true], 'm/h' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true], 'm/hr' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true], 'mph' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Miles per hour', 'AllowPrefix' => false], 'admkn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Admiralty Knot', 'AllowPrefix' => false], 'kn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Knot', 'AllowPrefix' => false], ]; /** * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). * * @var mixed[] */ private static $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 mixed[] */ private static $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 mixed[] */ private static $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. * * @return array */ public static function getConversionCategories() { $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 * * @return array */ public static function getConversionCategoryUnits($category = null) { $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 * * @return array */ public static function getConversionCategoryUnitDetails($category = null) { $conversionGroups = []; foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { if (($category === null) || ($conversionGroup['Group'] == $category)) { $conversionGroups[$conversionGroup['Group']][] = [ 'unit' => $conversionUnit, 'description' => $conversionGroup['Unit Name'], ]; } } 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() { 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() { 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 $e) { 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, /** @scrutinizer ignore-type */ $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'); } /** * @param float|int $value * * @return float|int */ protected static function convertTemperature(string $fromUOM, string $toUOM, $value) { $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 { switch ($uom) { case 'fah': return 'F'; case 'cel': return 'C'; case 'kel': return 'K'; } return $uom; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php000064400000003666152427537250022734 0ustar00 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/ComplexFunctions.php000064400000046176152427537250024024 0ustar00abs(); } /** * 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($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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|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 IMCOS($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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|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 IMCOSH($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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|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 IMCOT($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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|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 IMCSC($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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|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 IMCSCH($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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|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 IMSIN($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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|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 IMSINH($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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|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 IMSEC($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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|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 IMSECH($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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|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 IMTAN($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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($complexNumber, $realNumber) { if (is_array($complexNumber) || is_array($realNumber)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber, $realNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { return ExcelError::NAN(); } if (!is_numeric($realNumber)) { return ExcelError::VALUE(); } return (string) $complex->pow((float) $realNumber); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php000064400000016615152427537250023122 0ustar00getMessage(); } return ConvertDecimal::toBinary(self::toDecimal($value), $places); } /** * toDecimal. * * Return an octal value as decimal. * * Excel Function: * OCT2DEC(x) * * @param array|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|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) { 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/BesselK.php000064400000010370152427537250022037 0ustar00getMessage(); } 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 { // special cases switch ($ord) { case 0: return self::besselK0($x); case 1: return self::besselK1($x); } return 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/BesselI.php000064400000011243152427537250022035 0ustar00getMessage(); } 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 { // special cases switch ($ord) { case 0: return self::besselI0($x); case 1: return self::besselI1($x); } return 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; } /** * Sop to Scrutinizer. * * @var float */ private static $zeroPointZero = 0.0; private static function besselI2(float $x, int $ord): float { if ($x === self::$zeroPointZero) { 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/BitWise.php000064400000020134152427537250022054 0ustar00getMessage(); } $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 array|int $number1 * Or can be an array of values * @param array|int $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($number1, $number2) { 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 array|int $number1 * Or can be an array of values * @param array|int $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($number1, $number2) { 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 array|int $number * Or can be an array of values * @param array|int $shiftAmount * 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 BITLSHIFT($number, $shiftAmount) { 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 array|int $number * Or can be an array of values * @param array|int $shiftAmount * 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 BITRSHIFT($number, $shiftAmount) { 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. * * @param mixed $value * * @return float */ private static function validateBitwiseArgument($value) { $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. * * @param mixed $value * * @return int */ private static function validateShiftAmount($value) { $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. * * @param mixed $number * * @return mixed */ private static function nullFalseTrueToNumber(&$number) { if ($number === null) { $number = 0; } elseif (is_bool($number)) { $number = (int) $number; } return $number; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Erf.php000064400000006723152427537250021232 0ustar00 2.2) { return 1 - 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/ConvertDecimal.php000064400000022401152427537250023404 0ustar00 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 array|int $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) { 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|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 array|int $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) { 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|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) { 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/ConvertBinary.php000064400000016134152427537250023300 0ustar00getMessage(); } 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|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 array|int $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) { 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|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 array|int $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) { 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/ComplexOperations.php000064400000010511152427537250024157 0ustar00divideby(new ComplexObject($complexDivisor)); } catch (ComplexException $e) { 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($complexNumber1, $complexNumber2) { 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 $e) { 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 * * @return string */ public static function IMSUM(...$complexNumbers) { // 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 $e) { 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 * * @return string */ public static function IMPRODUCT(...$complexNumbers) { // 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 $e) { return ExcelError::NAN(); } return (string) $returnValue; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php000064400000004623152427537250021332 0ustar00 Functions::PRECISION); return self::ONE_SQRT_PI * exp(-$value * $value) * $q2; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php000064400000001311152427537250025132 0ustar00getMessage(); } $dec = self::toDecimal($value); return ConvertDecimal::toBinary($dec, $places); } /** * toDecimal. * * Return a hex value as decimal. * * Excel Function: * HEX2DEC(x) * * @param array|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|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) { 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/Complex.php000064400000010224152427537250022114 0ustar00getMessage(); } 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) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { 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) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $e) { return ExcelError::NAN(); } return $complex->getReal(); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php000064400000005620152427537250022077 0ustar00getMessage(); } 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|float $number the value to test against step * Or can be an array of values * @param null|array|float $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($number, $step = 0.0) { 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/BesselJ.php000064400000013442152427537250022041 0ustar00 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($x, $ord) { 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 { // special cases switch ($ord) { case 0: return self::besselJ0($x); case 1: return self::besselJ1($x); } return 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/Web/Service.php000064400000003661152427537250020377 0ustar00 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 $e) { 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) * * @param mixed $text * * @return string the url encoded output */ public static function urlEncode($text) { if (!is_string($text)) { return ExcelError::VALUE(); } return str_replace('+', '%20', urlencode($text)); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php000064400000007107152427537250023162 0ustar00 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 * * @return float|string */ public static function harmonic(...$args) { // 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 * * @return float|string */ public static function trim(...$args) { $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/Distributions/LogNormal.php000064400000012662152427537250025303 0ustar00getMessage(); } 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($value, $mean, $stdDev, $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($probability, $mean, $stdDev) { 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 = StandardNormal::inverse($probability); return exp($mean + $stdDev * $inverse); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php000064400000003262152427537250026212 0ustar00callback = $callback; } /** @return float|string */ public function execute(float $probability) { $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/Exponential.php000064400000004035152427537250025672 0ustar00getMessage(); } 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/Fisher.php000064400000004636152427537250024633 0ustar00getMessage(); } 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($probability) { 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/StandardNormal.php000064400000013754152427537250026325 0ustar00 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 /** @var float */ private static $logGammaCacheResult = 0.0; /** @var float */ private static $logGammaCacheX = 0.0; /** * logGamma function. * * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher. * * The natural logarithm of the gamma function.
* Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz
* Applied Mathematics Division
* Argonne National Laboratory
* Argonne, IL 60439
*

* References: *

    *
  1. W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.
  2. *
  3. K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.
  4. *
  5. Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.
  6. *
*

*

* From the original documentation: *

*

* 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. *

*

* Error returns:
* 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. *

* * @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/Distributions/Beta.php000064400000022717152427537250024266 0ustar00getMessage(); } 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($probability, $alpha, $beta, $rMin = 0.0, $rMax = 1.0) { 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); } /** * @return float|string */ private static function calculateInverse(float $probability, float $alpha, float $beta, float $rMin, float $rMax) { $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 /** @var float */ private static $logBetaCacheP = 0.0; /** @var float */ private static $logBetaCacheQ = 0.0; /** @var float */ private static $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/ChiSquared.php000064400000025363152427537250025443 0ustar00getMessage(); } 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|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($value, $degrees, $cumulative) { 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) { return 1 - self::distributionRightTail($value, $degrees); } 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($probability, $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) { 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($probability, $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(); } 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 * * @return float|string */ public static function test($actual, $expected) { $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 = 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 = 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/Binomial.php000064400000023273152427537250025143 0ustar00getMessage(); } if (($value < 0) || ($value > $trials)) { return ExcelError::NAN(); } if ($cumulative) { return self::calculateCumulativeBinomial($value, $trials, $probability); } /** @var float */ $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|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($trials, $probability, $successes, $limit = null) { 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 = 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($failures, $successes, $probability) { 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 = 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($trials, $probability, $alpha) { 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; } /** * @return float|int */ private static function calculateCumulativeBinomial(int $value, int $trials, float $probability) { $summer = 0; for ($i = 0; $i <= $value; ++$i) { /** @var float */ $comb = Combinations::withoutRepetition($trials, $i); $summer += $comb * $probability ** $i * (1 - $probability) ** ($trials - $i); } return $summer; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php000064400000001246152427537250030262 0ustar00 1.0) { throw new Exception(ExcelError::NAN()); } return $probability; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php000064400000004543152427537250025042 0ustar00getMessage(); } if (($value < 0) || ($mean < 0)) { return ExcelError::NAN(); } if ($cumulative) { $summer = 0; $floor = floor($value); for ($i = 0; $i <= $floor; ++$i) { /** @var float */ $fact = MathTrig\Factorial::fact($i); $summer += $mean ** $i / $fact; } return exp(0 - $mean) * $summer; } /** @var float */ $fact = MathTrig\Factorial::fact($value); return (exp(0 - $mean) * $mean ** $value) / $fact; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php000064400000006325152427537250026336 0ustar00getMessage(); } 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/Normal.php000064400000015774152427537250024650 0ustar00getMessage(); } 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($probability, $mean, $stdDev) { 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/Gamma.php000064400000012265152427537250024432 0ustar00getMessage(); } 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($value, $a, $b, $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($probability, $alpha, $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($value) { 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/F.php000064400000005056152427537250023575 0ustar00getMessage(); } 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/Weibull.php000064400000004233152427537250025007 0ustar00getMessage(); } 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/StudentT.php000064400000011321152427537250025152 0ustar00getMessage(); } 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($probability, $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 = function ($value) use ($degrees) { return self::distribution($value, $degrees, 2); }; $newtonRaphson = new NewtonRaphson($callback); return $newtonRaphson->execute($probability); } /** * @return float */ private static function calculateDistribution(float $value, int $degrees, int $tails) { // 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/AggregateBase.php000064400000004066152427537250023227 0ustar00 $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 * * @return float */ public static function maxA(...$args) { $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/Size.php000064400000004704152427537250021457 0ustar00= $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(...$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/Statistical/Trends.php000064400000034246152427537250022010 0ustar00 $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(&$array1, &$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 * * @return float|string */ public static function CORREL($yValues, $xValues = null) { 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 * * @return float|string */ public static function COVAR($yValues, $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->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 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($xValue, $yValues, $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 float[] */ public static function GROWTH($yValues, $xValues = [], $newValues = [], $const = true) { $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; //* @phpstan-ignore-line } /** * 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 * * @return float|string */ public static function INTERCEPT($yValues, $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->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|int|string The result, or a string containing an error */ public static function LINEST($yValues, $xValues = null, $const = true, $stats = false) { $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|int|string The result, or a string containing an error */ public static function LOGEST($yValues, $xValues = null, $const = true, $stats = false) { $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($yValues, $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($yValues, $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 * * @return float|string */ public static function STEYX($yValues, $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->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 float[] */ public static function TREND($yValues, $xValues = [], $newValues = [], $const = true) { $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; //* @phpstan-ignore-line } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php000064400000014343152427537250023022 0ustar00getMessage(); } 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($valueSet, $value, $significance = 3) { $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(...$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($value, $valueSet, $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, function ($value): bool { return is_numeric($value) && !is_string($value); } ); } protected static function rankFilterValues(array $dataSet): array { return array_filter( $dataSet, function ($value): bool { return is_numeric($value); } ); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Variances.php000064400000011755152427537250022464 0ustar00 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(...$args) { $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(...$args) { // 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(...$args) { $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/StatisticalValidations.php000064400000001715152427537250025226 0ustar00 $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|string (string if result is an error) */ public static function average(...$args) { $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|string (string if result is an error) */ public static function averageA(...$args) { $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(...$args) { $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(...$args) { $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) { // 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. * * @return float|string */ private static function modeCalc(array $data) { $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/Deviations.php000064400000010563152427537250022652 0ustar00 $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 * * @return float|string */ public static function kurtosis(...$args) { $aArgs = Functions::flattenArrayIndexed($args); $mean = Averages::average($aArgs); if (!is_numeric($mean)) { return ExcelError::DIV0(); } $stdDev = 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) { $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/VarianceBase.php000064400000001425152427537250023065 0ustar00getMessage(); } if ($stdDev <= 0) { return ExcelError::NAN(); } return ($value - $mean) / $stdDev; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php000064400000004416152427537250022160 0ustar00 $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 * * @return int */ public static function COUNTA(...$args) { $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 * * @return int */ public static function COUNTBLANK($range) { 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/Permutations.php000064400000007240152427537250023235 0ustar00getMessage(); } if ($numObjs < $numInSet) { return ExcelError::NAN(); } $result1 = MathTrig\Factorial::fact($numObjs); if (is_string($result1)) { return $result1; } $result2 = MathTrig\Factorial::fact($numObjs - $numInSet); if (is_string($result2)) { return $result2; } // phpstan thinks result1 and result2 can be arrays; they can't. $result = round($result1 / $result2); // @phpstan-ignore-line 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($numObjs, $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/MaxMinBase.php000064400000000631152427537250022524 0ustar00getMessage(); } if (($alpha <= 0) || ($alpha >= 1) || ($stdDev <= 0) || ($size < 1)) { return ExcelError::NAN(); } /** @var float */ $temp = Distributions\StandardNormal::inverse(1 - $alpha / 2); return Functions::scalar($temp * $stdDev / sqrt($size)); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions000064400000026520152427537250021355 0ustar00############################################################ ## ## PhpSpreadsheet - function name translations ## ## Jezyk polski (Polish) ## ############################################################ ## ## Funkcje baz danych (Cube Functions) ## CUBEKPIMEMBER = ELEMENT.KPI.MODUŁU CUBEMEMBER = ELEMENT.MODUŁU CUBEMEMBERPROPERTY = WŁAŚCIWOŚĆ.ELEMENTU.MODUŁU CUBERANKEDMEMBER = USZEREGOWANY.ELEMENT.MODUŁU CUBESET = ZESTAW.MODUŁÓW CUBESETCOUNT = LICZNIK.MODUŁÓW.ZESTAWU CUBEVALUE = WARTOŚĆ.MODUŁU ## ## Funkcje baz danych (Database Functions) ## DAVERAGE = BD.ŚREDNIA DCOUNT = BD.ILE.REKORDÓW DCOUNTA = BD.ILE.REKORDÓW.A DGET = BD.POLE DMAX = BD.MAX DMIN = BD.MIN DPRODUCT = BD.ILOCZYN DSTDEV = BD.ODCH.STANDARD DSTDEVP = BD.ODCH.STANDARD.POPUL DSUM = BD.SUMA DVAR = BD.WARIANCJA DVARP = BD.WARIANCJA.POPUL ## ## Funkcje daty i godziny (Date & Time Functions) ## DATE = DATA DATEDIF = DATA.RÓŻNICA DATESTRING = DATA.CIĄG.ZNAK DATEVALUE = DATA.WARTOŚĆ DAY = DZIEŃ DAYS = DNI DAYS360 = DNI.360 EDATE = NR.SER.DATY EOMONTH = NR.SER.OST.DN.MIES HOUR = GODZINA ISOWEEKNUM = ISO.NUM.TYG MINUTE = MINUTA MONTH = MIESIĄC NETWORKDAYS = DNI.ROBOCZE NETWORKDAYS.INTL = DNI.ROBOCZE.NIESTAND NOW = TERAZ SECOND = SEKUNDA THAIDAYOFWEEK = TAJ.DZIEŃ.TYGODNIA THAIMONTHOFYEAR = TAJ.MIESIĄC.ROKU THAIYEAR = TAJ.ROK TIME = CZAS TIMEVALUE = CZAS.WARTOŚĆ TODAY = DZIŚ WEEKDAY = DZIEŃ.TYG WEEKNUM = NUM.TYG WORKDAY = DZIEŃ.ROBOCZY WORKDAY.INTL = DZIEŃ.ROBOCZY.NIESTAND YEAR = ROK YEARFRAC = CZĘŚĆ.ROKU ## ## Funkcje inżynierskie (Engineering Functions) ## BESSELI = BESSEL.I BESSELJ = BESSEL.J BESSELK = BESSEL.K BESSELY = BESSEL.Y BIN2DEC = DWÓJK.NA.DZIES BIN2HEX = DWÓJK.NA.SZESN BIN2OCT = DWÓJK.NA.ÓSM BITAND = BITAND BITLSHIFT = BIT.PRZESUNIĘCIE.W.LEWO BITOR = BITOR BITRSHIFT = BIT.PRZESUNIĘCIE.W.PRAWO BITXOR = BITXOR COMPLEX = LICZBA.ZESP CONVERT = KONWERTUJ DEC2BIN = DZIES.NA.DWÓJK DEC2HEX = DZIES.NA.SZESN DEC2OCT = DZIES.NA.ÓSM DELTA = CZY.RÓWNE ERF = FUNKCJA.BŁ ERF.PRECISE = FUNKCJA.BŁ.DOKŁ ERFC = KOMP.FUNKCJA.BŁ ERFC.PRECISE = KOMP.FUNKCJA.BŁ.DOKŁ GESTEP = SPRAWDŹ.PRÓG HEX2BIN = SZESN.NA.DWÓJK HEX2DEC = SZESN.NA.DZIES HEX2OCT = SZESN.NA.ÓSM IMABS = MODUŁ.LICZBY.ZESP IMAGINARY = CZ.UROJ.LICZBY.ZESP IMARGUMENT = ARG.LICZBY.ZESP IMCONJUGATE = SPRZĘŻ.LICZBY.ZESP IMCOS = COS.LICZBY.ZESP IMCOSH = COSH.LICZBY.ZESP IMCOT = COT.LICZBY.ZESP IMCSC = CSC.LICZBY.ZESP IMCSCH = CSCH.LICZBY.ZESP IMDIV = ILORAZ.LICZB.ZESP IMEXP = EXP.LICZBY.ZESP IMLN = LN.LICZBY.ZESP IMLOG10 = LOG10.LICZBY.ZESP IMLOG2 = LOG2.LICZBY.ZESP IMPOWER = POTĘGA.LICZBY.ZESP IMPRODUCT = ILOCZYN.LICZB.ZESP IMREAL = CZ.RZECZ.LICZBY.ZESP IMSEC = SEC.LICZBY.ZESP IMSECH = SECH.LICZBY.ZESP IMSIN = SIN.LICZBY.ZESP IMSINH = SINH.LICZBY.ZESP IMSQRT = PIERWIASTEK.LICZBY.ZESP IMSUB = RÓŻN.LICZB.ZESP IMSUM = SUMA.LICZB.ZESP IMTAN = TAN.LICZBY.ZESP OCT2BIN = ÓSM.NA.DWÓJK OCT2DEC = ÓSM.NA.DZIES OCT2HEX = ÓSM.NA.SZESN ## ## Funkcje finansowe (Financial Functions) ## ACCRINT = NAL.ODS ACCRINTM = NAL.ODS.WYKUP AMORDEGRC = AMORT.NIELIN AMORLINC = AMORT.LIN COUPDAYBS = WYPŁ.DNI.OD.POCZ COUPDAYS = WYPŁ.DNI COUPDAYSNC = WYPŁ.DNI.NAST COUPNCD = WYPŁ.DATA.NAST COUPNUM = WYPŁ.LICZBA COUPPCD = WYPŁ.DATA.POPRZ CUMIPMT = SPŁAC.ODS CUMPRINC = SPŁAC.KAPIT DB = DB DDB = DDB DISC = STOPA.DYSK DOLLARDE = CENA.DZIES DOLLARFR = CENA.UŁAM DURATION = ROCZ.PRZYCH EFFECT = EFEKTYWNA FV = FV FVSCHEDULE = WART.PRZYSZŁ.KAP INTRATE = STOPA.PROC IPMT = IPMT IRR = IRR ISPMT = ISPMT MDURATION = ROCZ.PRZYCH.M MIRR = MIRR NOMINAL = NOMINALNA NPER = NPER NPV = NPV ODDFPRICE = CENA.PIERW.OKR ODDFYIELD = RENT.PIERW.OKR ODDLPRICE = CENA.OST.OKR ODDLYIELD = RENT.OST.OKR PDURATION = O.CZAS.TRWANIA PMT = PMT PPMT = PPMT PRICE = CENA PRICEDISC = CENA.DYSK PRICEMAT = CENA.WYKUP PV = PV RATE = RATE RECEIVED = KWOTA.WYKUP RRI = RÓWNOW.STOPA.PROC SLN = SLN SYD = SYD TBILLEQ = RENT.EKW.BS TBILLPRICE = CENA.BS TBILLYIELD = RENT.BS VDB = VDB XIRR = XIRR XNPV = XNPV YIELD = RENTOWNOŚĆ YIELDDISC = RENT.DYSK YIELDMAT = RENT.WYKUP ## ## Funkcje informacyjne (Information Functions) ## CELL = KOMÓRKA ERROR.TYPE = NR.BŁĘDU INFO = INFO ISBLANK = CZY.PUSTA ISERR = CZY.BŁ ISERROR = CZY.BŁĄD ISEVEN = CZY.PARZYSTE ISFORMULA = CZY.FORMUŁA ISLOGICAL = CZY.LOGICZNA ISNA = CZY.BRAK ISNONTEXT = CZY.NIE.TEKST ISNUMBER = CZY.LICZBA ISODD = CZY.NIEPARZYSTE ISREF = CZY.ADR ISTEXT = CZY.TEKST N = N NA = BRAK SHEET = ARKUSZ SHEETS = ARKUSZE TYPE = TYP ## ## Funkcje logiczne (Logical Functions) ## AND = ORAZ FALSE = FAŁSZ IF = JEŻELI IFERROR = JEŻELI.BŁĄD IFNA = JEŻELI.ND IFS = WARUNKI NOT = NIE OR = LUB SWITCH = PRZEŁĄCZ TRUE = PRAWDA XOR = XOR ## ## Funkcje wyszukiwania i odwołań (Lookup & Reference Functions) ## ADDRESS = ADRES AREAS = OBSZARY CHOOSE = WYBIERZ COLUMN = NR.KOLUMNY COLUMNS = LICZBA.KOLUMN FORMULATEXT = FORMUŁA.TEKST GETPIVOTDATA = WEŹDANETABELI HLOOKUP = WYSZUKAJ.POZIOMO HYPERLINK = HIPERŁĄCZE INDEX = INDEKS INDIRECT = ADR.POŚR LOOKUP = WYSZUKAJ MATCH = PODAJ.POZYCJĘ OFFSET = PRZESUNIĘCIE ROW = WIERSZ ROWS = ILE.WIERSZY RTD = DANE.CZASU.RZECZ TRANSPOSE = TRANSPONUJ VLOOKUP = WYSZUKAJ.PIONOWO ## ## Funkcje matematyczne i trygonometryczne (Math & Trig Functions) ## ABS = MODUŁ.LICZBY ACOS = ACOS ACOSH = ACOSH ACOT = ACOT ACOTH = ACOTH AGGREGATE = AGREGUJ ARABIC = ARABSKIE ASIN = ASIN ASINH = ASINH ATAN = ATAN ATAN2 = ATAN2 ATANH = ATANH BASE = PODSTAWA CEILING.MATH = ZAOKR.W.GÓRĘ.MATEMATYCZNE CEILING.PRECISE = ZAOKR.W.GÓRĘ.DOKŁ COMBIN = KOMBINACJE COMBINA = KOMBINACJE.A COS = COS COSH = COSH COT = COT COTH = COTH CSC = CSC CSCH = CSCH DECIMAL = DZIESIĘTNA DEGREES = STOPNIE ECMA.CEILING = ECMA.ZAOKR.W.GÓRĘ EVEN = ZAOKR.DO.PARZ EXP = EXP FACT = SILNIA FACTDOUBLE = SILNIA.DWUKR FLOOR.MATH = ZAOKR.W.DÓŁ.MATEMATYCZNE FLOOR.PRECISE = ZAOKR.W.DÓŁ.DOKŁ GCD = NAJW.WSP.DZIEL INT = ZAOKR.DO.CAŁK ISO.CEILING = ISO.ZAOKR.W.GÓRĘ LCM = NAJMN.WSP.WIEL LN = LN LOG = LOG LOG10 = LOG10 MDETERM = WYZNACZNIK.MACIERZY MINVERSE = MACIERZ.ODW MMULT = MACIERZ.ILOCZYN MOD = MOD MROUND = ZAOKR.DO.WIELOKR MULTINOMIAL = WIELOMIAN MUNIT = MACIERZ.JEDNOSTKOWA ODD = ZAOKR.DO.NPARZ PI = PI POWER = POTĘGA PRODUCT = ILOCZYN QUOTIENT = CZ.CAŁK.DZIELENIA RADIANS = RADIANY RAND = LOS RANDBETWEEN = LOS.ZAKR ROMAN = RZYMSKIE ROUND = ZAOKR ROUNDBAHTDOWN = ZAOKR.DÓŁ.BAT ROUNDBAHTUP = ZAOKR.GÓRA.BAT ROUNDDOWN = ZAOKR.DÓŁ ROUNDUP = ZAOKR.GÓRA SEC = SEC SECH = SECH SERIESSUM = SUMA.SZER.POT SIGN = ZNAK.LICZBY SIN = SIN SINH = SINH SQRT = PIERWIASTEK SQRTPI = PIERW.PI SUBTOTAL = SUMY.CZĘŚCIOWE SUM = SUMA SUMIF = SUMA.JEŻELI SUMIFS = SUMA.WARUNKÓW SUMPRODUCT = SUMA.ILOCZYNÓW SUMSQ = SUMA.KWADRATÓW SUMX2MY2 = SUMA.X2.M.Y2 SUMX2PY2 = SUMA.X2.P.Y2 SUMXMY2 = SUMA.XMY.2 TAN = TAN TANH = TANH TRUNC = LICZBA.CAŁK ## ## Funkcje statystyczne (Statistical Functions) ## AVEDEV = ODCH.ŚREDNIE AVERAGE = ŚREDNIA AVERAGEA = ŚREDNIA.A AVERAGEIF = ŚREDNIA.JEŻELI AVERAGEIFS = ŚREDNIA.WARUNKÓW BETA.DIST = ROZKŁ.BETA BETA.INV = ROZKŁ.BETA.ODWR BINOM.DIST = ROZKŁ.DWUM BINOM.DIST.RANGE = ROZKŁ.DWUM.ZAKRES BINOM.INV = ROZKŁ.DWUM.ODWR CHISQ.DIST = ROZKŁ.CHI CHISQ.DIST.RT = ROZKŁ.CHI.PS CHISQ.INV = ROZKŁ.CHI.ODWR CHISQ.INV.RT = ROZKŁ.CHI.ODWR.PS CHISQ.TEST = CHI.TEST CONFIDENCE.NORM = UFNOŚĆ.NORM CONFIDENCE.T = UFNOŚĆ.T CORREL = WSP.KORELACJI COUNT = ILE.LICZB COUNTA = ILE.NIEPUSTYCH COUNTBLANK = LICZ.PUSTE COUNTIF = LICZ.JEŻELI COUNTIFS = LICZ.WARUNKI COVARIANCE.P = KOWARIANCJA.POPUL COVARIANCE.S = KOWARIANCJA.PRÓBKI DEVSQ = ODCH.KWADRATOWE EXPON.DIST = ROZKŁ.EXP F.DIST = ROZKŁ.F F.DIST.RT = ROZKŁ.F.PS F.INV = ROZKŁ.F.ODWR F.INV.RT = ROZKŁ.F.ODWR.PS F.TEST = F.TEST FISHER = ROZKŁAD.FISHER FISHERINV = ROZKŁAD.FISHER.ODW FORECAST.ETS = REGLINX.ETS FORECAST.ETS.CONFINT = REGLINX.ETS.CONFINT FORECAST.ETS.SEASONALITY = REGLINX.ETS.SEZONOWOŚĆ FORECAST.ETS.STAT = REGLINX.ETS.STATYSTYKA FORECAST.LINEAR = REGLINX.LINIOWA FREQUENCY = CZĘSTOŚĆ GAMMA = GAMMA GAMMA.DIST = ROZKŁ.GAMMA GAMMA.INV = ROZKŁ.GAMMA.ODWR GAMMALN = ROZKŁAD.LIN.GAMMA GAMMALN.PRECISE = ROZKŁAD.LIN.GAMMA.DOKŁ GAUSS = GAUSS GEOMEAN = ŚREDNIA.GEOMETRYCZNA GROWTH = REGEXPW HARMEAN = ŚREDNIA.HARMONICZNA HYPGEOM.DIST = ROZKŁ.HIPERGEOM INTERCEPT = ODCIĘTA KURT = KURTOZA LARGE = MAX.K LINEST = REGLINP LOGEST = REGEXPP LOGNORM.DIST = ROZKŁ.LOG LOGNORM.INV = ROZKŁ.LOG.ODWR MAX = MAX MAXA = MAX.A MAXIFS = MAKS.WARUNKÓW MEDIAN = MEDIANA MIN = MIN MINA = MIN.A MINIFS = MIN.WARUNKÓW MODE.MULT = WYST.NAJCZĘŚCIEJ.TABL MODE.SNGL = WYST.NAJCZĘŚCIEJ.WART NEGBINOM.DIST = ROZKŁ.DWUM.PRZEC NORM.DIST = ROZKŁ.NORMALNY NORM.INV = ROZKŁ.NORMALNY.ODWR NORM.S.DIST = ROZKŁ.NORMALNY.S NORM.S.INV = ROZKŁ.NORMALNY.S.ODWR PEARSON = PEARSON PERCENTILE.EXC = PERCENTYL.PRZEDZ.OTW PERCENTILE.INC = PERCENTYL.PRZEDZ.ZAMK PERCENTRANK.EXC = PROC.POZ.PRZEDZ.OTW PERCENTRANK.INC = PROC.POZ.PRZEDZ.ZAMK PERMUT = PERMUTACJE PERMUTATIONA = PERMUTACJE.A PHI = PHI POISSON.DIST = ROZKŁ.POISSON PROB = PRAWDPD QUARTILE.EXC = KWARTYL.PRZEDZ.OTW QUARTILE.INC = KWARTYL.PRZEDZ.ZAMK RANK.AVG = POZYCJA.ŚR RANK.EQ = POZYCJA.NAJW RSQ = R.KWADRAT SKEW = SKOŚNOŚĆ SKEW.P = SKOŚNOŚĆ.P SLOPE = NACHYLENIE SMALL = MIN.K STANDARDIZE = NORMALIZUJ STDEV.P = ODCH.STAND.POPUL STDEV.S = ODCH.STANDARD.PRÓBKI STDEVA = ODCH.STANDARDOWE.A STDEVPA = ODCH.STANDARD.POPUL.A STEYX = REGBŁSTD T.DIST = ROZKŁ.T T.DIST.2T = ROZKŁ.T.DS T.DIST.RT = ROZKŁ.T.PS T.INV = ROZKŁ.T.ODWR T.INV.2T = ROZKŁ.T.ODWR.DS T.TEST = T.TEST TREND = REGLINW TRIMMEAN = ŚREDNIA.WEWN VAR.P = WARIANCJA.POP VAR.S = WARIANCJA.PRÓBKI VARA = WARIANCJA.A VARPA = WARIANCJA.POPUL.A WEIBULL.DIST = ROZKŁ.WEIBULL Z.TEST = Z.TEST ## ## Funkcje tekstowe (Text Functions) ## BAHTTEXT = BAT.TEKST CHAR = ZNAK CLEAN = OCZYŚĆ CODE = KOD CONCAT = ZŁĄCZ.TEKST DOLLAR = KWOTA EXACT = PORÓWNAJ FIND = ZNAJDŹ FIXED = ZAOKR.DO.TEKST ISTHAIDIGIT = CZY.CYFRA.TAJ LEFT = LEWY LEN = DŁ LOWER = LITERY.MAŁE MID = FRAGMENT.TEKSTU NUMBERSTRING = LICZBA.CIĄG.ZNAK NUMBERVALUE = WARTOŚĆ.LICZBOWA PROPER = Z.WIELKIEJ.LITERY REPLACE = ZASTĄP REPT = POWT RIGHT = PRAWY SEARCH = SZUKAJ.TEKST SUBSTITUTE = PODSTAW T = T TEXT = TEKST TEXTJOIN = POŁĄCZ.TEKSTY THAIDIGIT = TAJ.CYFRA THAINUMSOUND = TAJ.DŹWIĘK.NUM THAINUMSTRING = TAJ.CIĄG.NUM THAISTRINGLENGTH = TAJ.DŁUGOŚĆ.CIĄGU TRIM = USUŃ.ZBĘDNE.ODSTĘPY UNICHAR = ZNAK.UNICODE UNICODE = UNICODE UPPER = LITERY.WIELKIE VALUE = WARTOŚĆ ## ## Funkcje sieci Web (Web Functions) ## ENCODEURL = ENCODEURL FILTERXML = FILTERXML WEBSERVICE = WEBSERVICE ## ## Funkcje zgodności (Compatibility Functions) ## BETADIST = ROZKŁAD.BETA BETAINV = ROZKŁAD.BETA.ODW BINOMDIST = ROZKŁAD.DWUM CEILING = ZAOKR.W.GÓRĘ CHIDIST = ROZKŁAD.CHI CHIINV = ROZKŁAD.CHI.ODW CHITEST = TEST.CHI CONCATENATE = ZŁĄCZ.TEKSTY CONFIDENCE = UFNOŚĆ COVAR = KOWARIANCJA CRITBINOM = PRÓG.ROZKŁAD.DWUM EXPONDIST = ROZKŁAD.EXP FDIST = ROZKŁAD.F FINV = ROZKŁAD.F.ODW FLOOR = ZAOKR.W.DÓŁ FORECAST = REGLINX FTEST = TEST.F GAMMADIST = ROZKŁAD.GAMMA GAMMAINV = ROZKŁAD.GAMMA.ODW HYPGEOMDIST = ROZKŁAD.HIPERGEOM LOGINV = ROZKŁAD.LOG.ODW LOGNORMDIST = ROZKŁAD.LOG MODE = WYST.NAJCZĘŚCIEJ NEGBINOMDIST = ROZKŁAD.DWUM.PRZEC NORMDIST = ROZKŁAD.NORMALNY NORMINV = ROZKŁAD.NORMALNY.ODW NORMSDIST = ROZKŁAD.NORMALNY.S NORMSINV = ROZKŁAD.NORMALNY.S.ODW PERCENTILE = PERCENTYL PERCENTRANK = PROCENT.POZYCJA POISSON = ROZKŁAD.POISSON QUARTILE = KWARTYL RANK = POZYCJA STDEV = ODCH.STANDARDOWE STDEVP = ODCH.STANDARD.POPUL TDIST = ROZKŁAD.T TINV = ROZKŁAD.T.ODW TTEST = TEST.T VAR = WARIANCJA VARP = WARIANCJA.POPUL WEIBULL = ROZKŁAD.WEIBULL ZTEST = TEST.Z phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config000064400000000517152427537250020610 0ustar00############################################################ ## ## PhpSpreadsheet - locale settings ## ## Jezyk polski (Polish) ## ############################################################ ArgumentSeparator = ; ## ## Error Codes ## NULL = #ZERO! DIV0 = #DZIEL/0! VALUE = #ARG! REF = #ADR! NAME = #NAZWA? NUM = #LICZBA! NA = #N/D! phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions000064400000025234152427537250021357 0ustar00############################################################ ## ## 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/config000064400000000455152427537250020612 0ustar00############################################################ ## ## 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/functions000064400000147436152427537250021344 0ustar00## ## PhpSpreadsheet ## ## ## Data in this file derived from information provided by web-junior (http://www.web-junior.net/) ## ## ## ## Add-in and Automation functions Функции надстроек и автоматизации ## GETPIVOTDATA = ПОЛУЧИТЬ.ДАННЫЕ.СВОДНОЙ.ТАБЛИЦЫ ## Возвращает данные, хранящиеся в отчете сводной таблицы. ## ## Cube functions Функции Куб ## CUBEKPIMEMBER = КУБЭЛЕМЕНТКИП ## Возвращает свойство ключевого индикатора производительности «(КИП)» и отображает имя «КИП» в ячейке. «КИП» представляет собой количественную величину, такую как ежемесячная валовая прибыль или ежеквартальная текучесть кадров, используемой для контроля эффективности работы организации. CUBEMEMBER = КУБЭЛЕМЕНТ ## Возвращает элемент или кортеж из куба. Используется для проверки существования элемента или кортежа в кубе. CUBEMEMBERPROPERTY = КУБСВОЙСТВОЭЛЕМЕНТА ## Возвращает значение свойства элемента из куба. Используется для проверки существования имени элемента в кубе и возвращает указанное свойство для этого элемента. CUBERANKEDMEMBER = КУБПОРЭЛЕМЕНТ ## Возвращает n-ый или ранжированный элемент в множество. Используется для возвращения одного или нескольких элементов в множество, например, лучшего продавца или 10 лучших студентов. CUBESET = КУБМНОЖ ## Определяет вычислительное множество элементов или кортежей, отправляя на сервер выражение, которое создает множество, а затем возвращает его в Microsoft Office Excel. CUBESETCOUNT = КУБЧИСЛОЭЛМНОЖ ## Возвращает число элементов множества. CUBEVALUE = КУБЗНАЧЕНИЕ ## Возвращает обобщенное значение из куба. ## ## Database functions Функции для работы с базами данных ## DAVERAGE = ДСРЗНАЧ ## Возвращает среднее значение выбранных записей базы данных. DCOUNT = БСЧЁТ ## Подсчитывает количество числовых ячеек в базе данных. DCOUNTA = БСЧЁТА ## Подсчитывает количество непустых ячеек в базе данных. DGET = БИЗВЛЕЧЬ ## Извлекает из базы данных одну запись, удовлетворяющую заданному условию. DMAX = ДМАКС ## Возвращает максимальное значение среди выделенных записей базы данных. DMIN = ДМИН ## Возвращает минимальное значение среди выделенных записей базы данных. DPRODUCT = БДПРОИЗВЕД ## Перемножает значения определенного поля в записях базы данных, удовлетворяющих условию. DSTDEV = ДСТАНДОТКЛ ## Оценивает стандартное отклонение по выборке для выделенных записей базы данных. DSTDEVP = ДСТАНДОТКЛП ## Вычисляет стандартное отклонение по генеральной совокупности для выделенных записей базы данных DSUM = БДСУММ ## Суммирует числа в поле для записей базы данных, удовлетворяющих условию. DVAR = БДДИСП ## Оценивает дисперсию по выборке из выделенных записей базы данных DVARP = БДДИСПП ## Вычисляет дисперсию по генеральной совокупности для выделенных записей базы данных ## ## Date and time functions Функции даты и времени ## DATE = ДАТА ## Возвращает заданную дату в числовом формате. DATEVALUE = ДАТАЗНАЧ ## Преобразует дату из текстового формата в числовой формат. DAY = ДЕНЬ ## Преобразует дату в числовом формате в день месяца. DAYS360 = ДНЕЙ360 ## Вычисляет количество дней между двумя датами на основе 360-дневного года. EDATE = ДАТАМЕС ## Возвращает дату в числовом формате, отстоящую на заданное число месяцев вперед или назад от начальной даты. EOMONTH = КОНМЕСЯЦА ## Возвращает дату в числовом формате для последнего дня месяца, отстоящего вперед или назад на заданное число месяцев. HOUR = ЧАС ## Преобразует дату в числовом формате в часы. MINUTE = МИНУТЫ ## Преобразует дату в числовом формате в минуты. MONTH = МЕСЯЦ ## Преобразует дату в числовом формате в месяцы. NETWORKDAYS = ЧИСТРАБДНИ ## Возвращает количество рабочих дней между двумя датами. NOW = ТДАТА ## Возвращает текущую дату и время в числовом формате. SECOND = СЕКУНДЫ ## Преобразует дату в числовом формате в секунды. TIME = ВРЕМЯ ## Возвращает заданное время в числовом формате. TIMEVALUE = ВРЕМЗНАЧ ## Преобразует время из текстового формата в числовой формат. TODAY = СЕГОДНЯ ## Возвращает текущую дату в числовом формате. WEEKDAY = ДЕНЬНЕД ## Преобразует дату в числовом формате в день недели. WEEKNUM = НОМНЕДЕЛИ ## Преобразует числовое представление в число, которое указывает, на какую неделю года приходится указанная дата. WORKDAY = РАБДЕНЬ ## Возвращает дату в числовом формате, отстоящую вперед или назад на заданное количество рабочих дней. YEAR = ГОД ## Преобразует дату в числовом формате в год. YEARFRAC = ДОЛЯГОДА ## Возвращает долю года, которую составляет количество дней между начальной и конечной датами. ## ## Engineering functions Инженерные функции ## BESSELI = БЕССЕЛЬ.I ## Возвращает модифицированную функцию Бесселя In(x). BESSELJ = БЕССЕЛЬ.J ## Возвращает функцию Бесселя Jn(x). BESSELK = БЕССЕЛЬ.K ## Возвращает модифицированную функцию Бесселя Kn(x). BESSELY = БЕССЕЛЬ.Y ## Возвращает функцию Бесселя Yn(x). 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 = МНИМ.ПРОИЗВЕД ## Возвращает произведение от 2 до 29 комплексных чисел. 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 = МДЛИТ ## Возвращает модифицированную длительность Маколея для ценных бумаг с предполагаемой номинальной стоимостью 100 рублей. MIRR = МВСД ## Возвращает внутреннюю ставку доходности, при которой положительные и отрицательные денежные потоки имеют разные значения ставки. NOMINAL = НОМИНАЛ ## Возвращает номинальную годовую процентную ставку. NPER = КПЕР ## Возвращает общее количество периодов выплаты для данного вклада. NPV = ЧПС ## Возвращает чистую приведенную стоимость инвестиции, основанной на серии периодических денежных потоков и ставке дисконтирования. ODDFPRICE = ЦЕНАПЕРВНЕРЕГ ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг с нерегулярным первым периодом. ODDFYIELD = ДОХОДПЕРВНЕРЕГ ## Возвращает доход по ценным бумагам с нерегулярным первым периодом. ODDLPRICE = ЦЕНАПОСЛНЕРЕГ ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг с нерегулярным последним периодом. ODDLYIELD = ДОХОДПОСЛНЕРЕГ ## Возвращает доход по ценным бумагам с нерегулярным последним периодом. PMT = ПЛТ ## Возвращает величину выплаты за один период аннуитета. PPMT = ОСПЛТ ## Возвращает величину выплат в погашение основной суммы по инвестиции за заданный период. PRICE = ЦЕНА ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг, по которым производится периодическая выплата процентов. PRICEDISC = ЦЕНАСКИДКА ## Возвращает цену за 100 рублей номинальной стоимости ценных бумаг, на которые сделана скидка. PRICEMAT = ЦЕНАПОГАШ ## Возвращает цену за 100 рублей номинальной стоимости ценных бумаг, проценты по которым выплачиваются в срок погашения. PV = ПС ## Возвращает приведенную (к текущему моменту) стоимость инвестиции. RATE = СТАВКА ## Возвращает процентную ставку по аннуитету за один период. RECEIVED = ПОЛУЧЕНО ## Возвращает сумму, полученную к сроку погашения полностью обеспеченных ценных бумаг. SLN = АПЛ ## Возвращает величину линейной амортизации актива за один период. SYD = АСЧ ## Возвращает величину амортизации актива за данный период, рассчитанную методом суммы годовых чисел. TBILLEQ = РАВНОКЧЕК ## Возвращает эквивалентный облигации доход по казначейскому чеку. TBILLPRICE = ЦЕНАКЧЕК ## Возвращает цену за 100 рублей нарицательной стоимости для казначейского чека. 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 = И ## Renvoie VRAI si tous ses arguments sont VRAI. FALSE = ЛОЖЬ ## Возвращает логическое значение ЛОЖЬ. IF = ЕСЛИ ## Выполняет проверку условия. IFERROR = ЕСЛИОШИБКА ## Возвращает введённое значение, если вычисление по формуле вызывает ошибку; в противном случае функция возвращает результат вычисления. NOT = НЕ ## Меняет логическое значение своего аргумента на противоположное. OR = ИЛИ ## Возвращает значение ИСТИНА, если хотя бы один аргумент имеет значение ИСТИНА. TRUE = ИСТИНА ## Возвращает логическое значение ИСТИНА. ## ## Lookup and reference functions Функции ссылки и поиска ## ADDRESS = АДРЕС ## Возвращает ссылку на отдельную ячейку листа в виде текста. AREAS = ОБЛАСТИ ## Возвращает количество областей в ссылке. CHOOSE = ВЫБОР ## Выбирает значение из списка значений по индексу. COLUMN = СТОЛБЕЦ ## Возвращает номер столбца, на который указывает ссылка. COLUMNS = ЧИСЛСТОЛБ ## Возвращает количество столбцов в ссылке. HLOOKUP = ГПР ## Ищет в первой строке массива и возвращает значение отмеченной ячейки HYPERLINK = ГИПЕРССЫЛКА ## Создает ссылку, открывающую документ, который находится на сервере сети, в интрасети или в Интернете. INDEX = ИНДЕКС ## Использует индекс для выбора значения из ссылки или массива. INDIRECT = ДВССЫЛ ## Возвращает ссылку, заданную текстовым значением. LOOKUP = ПРОСМОТР ## Ищет значения в векторе или массиве. MATCH = ПОИСКПОЗ ## Ищет значения в ссылке или массиве. OFFSET = СМЕЩ ## Возвращает смещение ссылки относительно заданной ссылки. ROW = СТРОКА ## Возвращает номер строки, определяемой ссылкой. ROWS = ЧСТРОК ## Возвращает количество строк в ссылке. RTD = ДРВ ## Извлекает данные реального времени из программ, поддерживающих автоматизацию COM (Программирование объектов. Стандартное средство для работы с объектами некоторого приложения из другого приложения или средства разработки. Программирование объектов (ранее называемое программированием OLE) является функцией модели COM (Component Object Model, модель компонентных объектов).). TRANSPOSE = ТРАНСП ## Возвращает транспонированный массив. VLOOKUP = ВПР ## Ищет значение в первом столбце массива и возвращает значение из ячейки в найденной строке и указанном столбце. ## ## Math and trigonometry functions Математические и тригонометрические функции ## ABS = ABS ## Возвращает модуль (абсолютную величину) числа. ACOS = ACOS ## Возвращает арккосинус числа. ACOSH = ACOSH ## Возвращает гиперболический арккосинус числа. ASIN = ASIN ## Возвращает арксинус числа. ASINH = ASINH ## Возвращает гиперболический арксинус числа. ATAN = ATAN ## Возвращает арктангенс числа. ATAN2 = ATAN2 ## Возвращает арктангенс для заданных координат x и y. ATANH = ATANH ## Возвращает гиперболический арктангенс числа. CEILING = ОКРВВЕРХ ## Округляет число до ближайшего целого или до ближайшего кратного указанному значению. COMBIN = ЧИСЛКОМБ ## Возвращает количество комбинаций для заданного числа объектов. COS = COS ## Возвращает косинус числа. COSH = COSH ## Возвращает гиперболический косинус числа. DEGREES = ГРАДУСЫ ## Преобразует радианы в градусы. EVEN = ЧЁТН ## Округляет число до ближайшего четного целого. EXP = EXP ## Возвращает число e, возведенное в указанную степень. FACT = ФАКТР ## Возвращает факториал числа. FACTDOUBLE = ДВФАКТР ## Возвращает двойной факториал числа. FLOOR = ОКРВНИЗ ## Округляет число до ближайшего меньшего по модулю значения. GCD = НОД ## Возвращает наибольший общий делитель. INT = ЦЕЛОЕ ## Округляет число до ближайшего меньшего целого. LCM = НОК ## Возвращает наименьшее общее кратное. LN = LN ## Возвращает натуральный логарифм числа. LOG = LOG ## Возвращает логарифм числа по заданному основанию. LOG10 = LOG10 ## Возвращает десятичный логарифм числа. MDETERM = МОПРЕД ## Возвращает определитель матрицы массива. MINVERSE = МОБР ## Возвращает обратную матрицу массива. MMULT = МУМНОЖ ## Возвращает произведение матриц двух массивов. MOD = ОСТАТ ## Возвращает остаток от деления. MROUND = ОКРУГЛТ ## Возвращает число, округленное с требуемой точностью. MULTINOMIAL = МУЛЬТИНОМ ## Возвращает мультиномиальный коэффициент множества чисел. ODD = НЕЧЁТ ## Округляет число до ближайшего нечетного целого. PI = ПИ ## Возвращает число пи. POWER = СТЕПЕНЬ ## Возвращает результат возведения числа в степень. PRODUCT = ПРОИЗВЕД ## Возвращает произведение аргументов. QUOTIENT = ЧАСТНОЕ ## Возвращает целую часть частного при делении. RADIANS = РАДИАНЫ ## Преобразует градусы в радианы. RAND = СЛЧИС ## Возвращает случайное число в интервале от 0 до 1. 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 = СРЗНАЧЕСЛИМН ## Возвращает среднее значение (среднее арифметическое) всех ячеек, которые удовлетворяют нескольким условиям. BETADIST = БЕТАРАСП ## Возвращает интегральную функцию бета-распределения. BETAINV = БЕТАОБР ## Возвращает обратную интегральную функцию указанного бета-распределения. BINOMDIST = БИНОМРАСП ## Возвращает отдельное значение биномиального распределения. CHIDIST = ХИ2РАСП ## Возвращает одностороннюю вероятность распределения хи-квадрат. CHIINV = ХИ2ОБР ## Возвращает обратное значение односторонней вероятности распределения хи-квадрат. CHITEST = ХИ2ТЕСТ ## Возвращает тест на независимость. CONFIDENCE = ДОВЕРИТ ## Возвращает доверительный интервал для среднего значения по генеральной совокупности. CORREL = КОРРЕЛ ## Возвращает коэффициент корреляции между двумя множествами данных. COUNT = СЧЁТ ## Подсчитывает количество чисел в списке аргументов. COUNTA = СЧЁТЗ ## Подсчитывает количество значений в списке аргументов. COUNTBLANK = СЧИТАТЬПУСТОТЫ ## Подсчитывает количество пустых ячеек в диапазоне COUNTIF = СЧЁТЕСЛИ ## Подсчитывает количество ячеек в диапазоне, удовлетворяющих заданному условию COUNTIFS = СЧЁТЕСЛИМН ## Подсчитывает количество ячеек внутри диапазона, удовлетворяющих нескольким условиям. COVAR = КОВАР ## Возвращает ковариацию, среднее произведений парных отклонений CRITBINOM = КРИТБИНОМ ## Возвращает наименьшее значение, для которого интегральное биномиальное распределение меньше или равно заданному критерию. DEVSQ = КВАДРОТКЛ ## Возвращает сумму квадратов отклонений. EXPONDIST = ЭКСПРАСП ## Возвращает экспоненциальное распределение. FDIST = FРАСП ## Возвращает F-распределение вероятности. FINV = FРАСПОБР ## Возвращает обратное значение для F-распределения вероятности. FISHER = ФИШЕР ## Возвращает преобразование Фишера. FISHERINV = ФИШЕРОБР ## Возвращает обратное преобразование Фишера. FORECAST = ПРЕДСКАЗ ## Возвращает значение линейного тренда. FREQUENCY = ЧАСТОТА ## Возвращает распределение частот в виде вертикального массива. FTEST = ФТЕСТ ## Возвращает результат F-теста. GAMMADIST = ГАММАРАСП ## Возвращает гамма-распределение. GAMMAINV = ГАММАОБР ## Возвращает обратное гамма-распределение. GAMMALN = ГАММАНЛОГ ## Возвращает натуральный логарифм гамма функции, Γ(x). GEOMEAN = СРГЕОМ ## Возвращает среднее геометрическое. GROWTH = РОСТ ## Возвращает значения в соответствии с экспоненциальным трендом. HARMEAN = СРГАРМ ## Возвращает среднее гармоническое. HYPGEOMDIST = ГИПЕРГЕОМЕТ ## Возвращает гипергеометрическое распределение. INTERCEPT = ОТРЕЗОК ## Возвращает отрезок, отсекаемый на оси линией линейной регрессии. KURT = ЭКСЦЕСС ## Возвращает эксцесс множества данных. LARGE = НАИБОЛЬШИЙ ## Возвращает k-ое наибольшее значение в множестве данных. LINEST = ЛИНЕЙН ## Возвращает параметры линейного тренда. LOGEST = ЛГРФПРИБЛ ## Возвращает параметры экспоненциального тренда. LOGINV = ЛОГНОРМОБР ## Возвращает обратное логарифмическое нормальное распределение. LOGNORMDIST = ЛОГНОРМРАСП ## Возвращает интегральное логарифмическое нормальное распределение. MAX = МАКС ## Возвращает наибольшее значение в списке аргументов. MAXA = МАКСА ## Возвращает наибольшее значение в списке аргументов, включая числа, текст и логические значения. MEDIAN = МЕДИАНА ## Возвращает медиану заданных чисел. MIN = МИН ## Возвращает наименьшее значение в списке аргументов. MINA = МИНА ## Возвращает наименьшее значение в списке аргументов, включая числа, текст и логические значения. MODE = МОДА ## Возвращает значение моды множества данных. NEGBINOMDIST = ОТРБИНОМРАСП ## Возвращает отрицательное биномиальное распределение. NORMDIST = НОРМРАСП ## Возвращает нормальную функцию распределения. NORMINV = НОРМОБР ## Возвращает обратное нормальное распределение. NORMSDIST = НОРМСТРАСП ## Возвращает стандартное нормальное интегральное распределение. NORMSINV = НОРМСТОБР ## Возвращает обратное значение стандартного нормального распределения. PEARSON = ПИРСОН ## Возвращает коэффициент корреляции Пирсона. PERCENTILE = ПЕРСЕНТИЛЬ ## Возвращает k-ую персентиль для значений диапазона. PERCENTRANK = ПРОЦЕНТРАНГ ## Возвращает процентную норму значения в множестве данных. PERMUT = ПЕРЕСТ ## Возвращает количество перестановок для заданного числа объектов. POISSON = ПУАССОН ## Возвращает распределение Пуассона. PROB = ВЕРОЯТНОСТЬ ## Возвращает вероятность того, что значение из диапазона находится внутри заданных пределов. QUARTILE = КВАРТИЛЬ ## Возвращает квартиль множества данных. RANK = РАНГ ## Возвращает ранг числа в списке чисел. RSQ = КВПИРСОН ## Возвращает квадрат коэффициента корреляции Пирсона. SKEW = СКОС ## Возвращает асимметрию распределения. SLOPE = НАКЛОН ## Возвращает наклон линии линейной регрессии. SMALL = НАИМЕНЬШИЙ ## Возвращает k-ое наименьшее значение в множестве данных. STANDARDIZE = НОРМАЛИЗАЦИЯ ## Возвращает нормализованное значение. STDEV = СТАНДОТКЛОН ## Оценивает стандартное отклонение по выборке. STDEVA = СТАНДОТКЛОНА ## Оценивает стандартное отклонение по выборке, включая числа, текст и логические значения. STDEVP = СТАНДОТКЛОНП ## Вычисляет стандартное отклонение по генеральной совокупности. STDEVPA = СТАНДОТКЛОНПА ## Вычисляет стандартное отклонение по генеральной совокупности, включая числа, текст и логические значения. STEYX = СТОШYX ## Возвращает стандартную ошибку предсказанных значений y для каждого значения x в регрессии. TDIST = СТЬЮДРАСП ## Возвращает t-распределение Стьюдента. TINV = СТЬЮДРАСПОБР ## Возвращает обратное t-распределение Стьюдента. TREND = ТЕНДЕНЦИЯ ## Возвращает значения в соответствии с линейным трендом. TRIMMEAN = УРЕЗСРЕДНЕЕ ## Возвращает среднее внутренности множества данных. TTEST = ТТЕСТ ## Возвращает вероятность, соответствующую критерию Стьюдента. VAR = ДИСП ## Оценивает дисперсию по выборке. VARA = ДИСПА ## Оценивает дисперсию по выборке, включая числа, текст и логические значения. VARP = ДИСПР ## Вычисляет дисперсию для генеральной совокупности. VARPA = ДИСПРА ## Вычисляет дисперсию для генеральной совокупности, включая числа, текст и логические значения. WEIBULL = ВЕЙБУЛЛ ## Возвращает распределение Вейбулла. ZTEST = ZТЕСТ ## Возвращает двустороннее P-значение z-теста. ## ## Text functions Текстовые функции ## ASC = ASC ## Для языков с двухбайтовыми наборами знаков (например, катакана) преобразует полноширинные (двухбайтовые) знаки в полуширинные (однобайтовые). BAHTTEXT = БАТТЕКСТ ## Преобразует число в текст, используя денежный формат ß (БАТ). CHAR = СИМВОЛ ## Возвращает знак с заданным кодом. CLEAN = ПЕЧСИМВ ## Удаляет все непечатаемые знаки из текста. CODE = КОДСИМВ ## Возвращает числовой код первого знака в текстовой строке. CONCATENATE = СЦЕПИТЬ ## Объединяет несколько текстовых элементов в один. DOLLAR = РУБЛЬ ## Преобразует число в текст, используя денежный формат. EXACT = СОВПАД ## Проверяет идентичность двух текстовых значений. FIND = НАЙТИ ## Ищет вхождения одного текстового значения в другом (с учетом регистра). FINDB = НАЙТИБ ## Ищет вхождения одного текстового значения в другом (с учетом регистра). FIXED = ФИКСИРОВАННЫЙ ## Форматирует число и преобразует его в текст с заданным числом десятичных знаков. JIS = JIS ## Для языков с двухбайтовыми наборами знаков (например, катакана) преобразует полуширинные (однобайтовые) знаки в текстовой строке в полноширинные (двухбайтовые). LEFT = ЛЕВСИМВ ## Возвращает крайние слева знаки текстового значения. LEFTB = ЛЕВБ ## Возвращает крайние слева знаки текстового значения. LEN = ДЛСТР ## Возвращает количество знаков в текстовой строке. LENB = ДЛИНБ ## Возвращает количество знаков в текстовой строке. LOWER = СТРОЧН ## Преобразует все буквы текста в строчные. MID = ПСТР ## Возвращает заданное число знаков из строки текста, начиная с указанной позиции. MIDB = ПСТРБ ## Возвращает заданное число знаков из строки текста, начиная с указанной позиции. PHONETIC = PHONETIC ## Извлекает фонетические (фуригана) знаки из текстовой строки. PROPER = ПРОПНАЧ ## Преобразует первую букву в каждом слове текста в прописную. REPLACE = ЗАМЕНИТЬ ## Заменяет знаки в тексте. REPLACEB = ЗАМЕНИТЬБ ## Заменяет знаки в тексте. REPT = ПОВТОР ## Повторяет текст заданное число раз. RIGHT = ПРАВСИМВ ## Возвращает крайние справа знаки текстовой строки. RIGHTB = ПРАВБ ## Возвращает крайние справа знаки текстовой строки. SEARCH = ПОИСК ## Ищет вхождения одного текстового значения в другом (без учета регистра). SEARCHB = ПОИСКБ ## Ищет вхождения одного текстового значения в другом (без учета регистра). SUBSTITUTE = ПОДСТАВИТЬ ## Заменяет в текстовой строке старый текст новым. T = Т ## Преобразует аргументы в текст. TEXT = ТЕКСТ ## Форматирует число и преобразует его в текст. TRIM = СЖПРОБЕЛЫ ## Удаляет из текста пробелы. UPPER = ПРОПИСН ## Преобразует все буквы текста в прописные. VALUE = ЗНАЧЕН ## Преобразует текстовый аргумент в число. phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/config000064400000000430152427537250020557 0ustar00## ## PhpSpreadsheet ## ## ## ArgumentSeparator = ; ## ## (For future use) ## currencySymbol = лв ## ## Excel Error Codes (For future use) ## NULL = #ПРАЗНО! DIV0 = #ДЕЛ/0! VALUE = #СТОЙНОСТ! REF = #РЕФ! NAME = #ИМЕ? NUM = #ЧИСЛО! NA = #Н/Д phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions000064400000023147152427537250021772 0ustar00############################################################ ## ## 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/config000064400000000520152427537250021215 0ustar00############################################################ ## ## 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/functions000064400000023777152427537250021400 0ustar00############################################################ ## ## 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/config000064400000000473152427537250020621 0ustar00############################################################ ## ## 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/sv/functions000064400000023241152427537250021367 0ustar00############################################################ ## ## 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/config000064400000000542152427537250020623 0ustar00############################################################ ## ## 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/fi/functions000064400000026262152427537250021343 0ustar00############################################################ ## ## 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/config000064400000000521152427537250020566 0ustar00############################################################ ## ## 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/ru/functions000064400000033315152427537250021370 0ustar00############################################################ ## ## 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/config000064400000000566152427537250020627 0ustar00############################################################ ## ## PhpSpreadsheet - locale settings ## ## русский язык (Russian) ## ############################################################ ArgumentSeparator = ; ## ## Error Codes ## NULL = #ПУСТО! DIV0 = #ДЕЛ/0! VALUE = #ЗНАЧ! REF = #ССЫЛКА! NAME = #ИМЯ? NUM = #ЧИСЛО! NA = #Н/Д phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions000064400000024411152427537250021364 0ustar00############################################################ ## ## PhpSpreadsheet - function name translations ## ## Türkçe (Turkish) ## ############################################################ ## ## Küp işlevleri (Cube Functions) ## CUBEKPIMEMBER = KÜPKPIÜYESİ CUBEMEMBER = KÜPÜYESİ CUBEMEMBERPROPERTY = KÜPÜYEÖZELLİĞİ CUBERANKEDMEMBER = DERECELİKÜPÜYESİ CUBESET = KÜPKÜMESİ CUBESETCOUNT = KÜPKÜMESAYISI CUBEVALUE = KÜPDEĞERİ ## ## Veritabanı işlevleri (Database Functions) ## DAVERAGE = VSEÇORT DCOUNT = VSEÇSAY DCOUNTA = VSEÇSAYDOLU DGET = VAL DMAX = VSEÇMAK DMIN = VSEÇMİN DPRODUCT = VSEÇÇARP DSTDEV = VSEÇSTDSAPMA DSTDEVP = VSEÇSTDSAPMAS DSUM = VSEÇTOPLA DVAR = VSEÇVAR DVARP = VSEÇVARS ## ## Tarih ve saat işlevleri (Date & Time Functions) ## DATE = TARİH DATEDIF = ETARİHLİ DATESTRING = TARİHDİZİ DATEVALUE = TARİHSAYISI DAY = GÜN DAYS = GÜNSAY DAYS360 = GÜN360 EDATE = SERİTARİH EOMONTH = SERİAY HOUR = SAAT ISOWEEKNUM = ISOHAFTASAY MINUTE = DAKİKA MONTH = AY NETWORKDAYS = TAMİŞGÜNÜ NETWORKDAYS.INTL = TAMİŞGÜNÜ.ULUSL NOW = ŞİMDİ SECOND = SANİYE THAIDAYOFWEEK = TAYHAFTANINGÜNÜ THAIMONTHOFYEAR = TAYYILINAYI THAIYEAR = TAYYILI TIME = ZAMAN TIMEVALUE = ZAMANSAYISI TODAY = BUGÜN WEEKDAY = HAFTANINGÜNÜ WEEKNUM = HAFTASAY WORKDAY = İŞGÜNÜ WORKDAY.INTL = İŞGÜNÜ.ULUSL YEAR = YIL YEARFRAC = YILORAN ## ## Mühendislik işlevleri (Engineering Functions) ## BESSELI = BESSELI BESSELJ = BESSELJ BESSELK = BESSELK BESSELY = BESSELY BIN2DEC = BIN2DEC BIN2HEX = BIN2HEX BIN2OCT = BIN2OCT BITAND = BİTVE BITLSHIFT = BİTSOLAKAYDIR BITOR = BİTVEYA BITRSHIFT = BİTSAĞAKAYDIR BITXOR = BİTÖZELVEYA COMPLEX = KARMAŞIK CONVERT = ÇEVİR DEC2BIN = DEC2BIN DEC2HEX = DEC2HEX DEC2OCT = DEC2OCT DELTA = DELTA ERF = HATAİŞLEV ERF.PRECISE = HATAİŞLEV.DUYARLI ERFC = TÜMHATAİŞLEV ERFC.PRECISE = TÜMHATAİŞLEV.DUYARLI GESTEP = BESINIR HEX2BIN = HEX2BIN HEX2DEC = HEX2DEC HEX2OCT = HEX2OCT IMABS = SANMUTLAK IMAGINARY = SANAL IMARGUMENT = SANBAĞ_DEĞİŞKEN IMCONJUGATE = SANEŞLENEK IMCOS = SANCOS IMCOSH = SANCOSH IMCOT = SANCOT IMCSC = SANCSC IMCSCH = SANCSCH IMDIV = SANBÖL IMEXP = SANÜS IMLN = SANLN IMLOG10 = SANLOG10 IMLOG2 = SANLOG2 IMPOWER = SANKUVVET IMPRODUCT = SANÇARP IMREAL = SANGERÇEK IMSEC = SANSEC IMSECH = SANSECH IMSIN = SANSIN IMSINH = SANSINH IMSQRT = SANKAREKÖK IMSUB = SANTOPLA IMSUM = SANÇIKAR IMTAN = SANTAN OCT2BIN = OCT2BIN OCT2DEC = OCT2DEC OCT2HEX = OCT2HEX ## ## Finansal işlevler (Financial Functions) ## ACCRINT = GERÇEKFAİZ ACCRINTM = GERÇEKFAİZV AMORDEGRC = AMORDEGRC AMORLINC = AMORLINC COUPDAYBS = KUPONGÜNBD COUPDAYS = KUPONGÜN COUPDAYSNC = KUPONGÜNDSK COUPNCD = KUPONGÜNSKT COUPNUM = KUPONSAYI COUPPCD = KUPONGÜNÖKT CUMIPMT = TOPÖDENENFAİZ CUMPRINC = TOPANAPARA DB = AZALANBAKİYE DDB = ÇİFTAZALANBAKİYE DISC = İNDİRİM DOLLARDE = LİRAON DOLLARFR = LİRAKES DURATION = SÜRE EFFECT = ETKİN FV = GD FVSCHEDULE = GDPROGRAM INTRATE = FAİZORANI IPMT = FAİZTUTARI IRR = İÇ_VERİM_ORANI ISPMT = ISPMT MDURATION = MSÜRE MIRR = D_İÇ_VERİM_ORANI NOMINAL = NOMİNAL NPER = TAKSİT_SAYISI NPV = NBD ODDFPRICE = TEKYDEĞER ODDFYIELD = TEKYÖDEME ODDLPRICE = TEKSDEĞER ODDLYIELD = TEKSÖDEME PDURATION = PSÜRE PMT = DEVRESEL_ÖDEME PPMT = ANA_PARA_ÖDEMESİ PRICE = DEĞER PRICEDISC = DEĞERİND PRICEMAT = DEĞERVADE PV = BD RATE = FAİZ_ORANI RECEIVED = GETİRİ RRI = GERÇEKLEŞENYATIRIMGETİRİSİ SLN = DA SYD = YAT TBILLEQ = HTAHEŞ TBILLPRICE = HTAHDEĞER TBILLYIELD = HTAHÖDEME VDB = DAB XIRR = AİÇVERİMORANI XNPV = ANBD YIELD = ÖDEME YIELDDISC = ÖDEMEİND YIELDMAT = ÖDEMEVADE ## ## Bilgi işlevleri (Information Functions) ## CELL = HÜCRE ERROR.TYPE = HATA.TİPİ INFO = BİLGİ ISBLANK = EBOŞSA ISERR = EHATA ISERROR = EHATALIYSA ISEVEN = ÇİFTMİ ISFORMULA = EFORMÜLSE ISLOGICAL = EMANTIKSALSA ISNA = EYOKSA ISNONTEXT = EMETİNDEĞİLSE ISNUMBER = ESAYIYSA ISODD = TEKMİ ISREF = EREFSE ISTEXT = EMETİNSE N = S NA = YOKSAY SHEET = SAYFA SHEETS = SAYFALAR TYPE = TÜR ## ## Mantıksal işlevler (Logical Functions) ## AND = VE FALSE = YANLIŞ IF = EĞER IFERROR = EĞERHATA IFNA = EĞERYOKSA IFS = ÇOKEĞER NOT = DEĞİL OR = YADA SWITCH = İLKEŞLEŞEN TRUE = DOĞRU XOR = ÖZELVEYA ## ## Arama ve başvuru işlevleri (Lookup & Reference Functions) ## ADDRESS = ADRES AREAS = ALANSAY CHOOSE = ELEMAN COLUMN = SÜTUN COLUMNS = SÜTUNSAY FORMULATEXT = FORMÜLMETNİ GETPIVOTDATA = ÖZETVERİAL HLOOKUP = YATAYARA HYPERLINK = KÖPRÜ INDEX = İNDİS INDIRECT = DOLAYLI LOOKUP = ARA MATCH = KAÇINCI OFFSET = KAYDIR ROW = SATIR ROWS = SATIRSAY RTD = GZV TRANSPOSE = DEVRİK_DÖNÜŞÜM VLOOKUP = DÜŞEYARA ## ## Matematik ve trigonometri işlevleri (Math & Trig Functions) ## ABS = MUTLAK ACOS = ACOS ACOSH = ACOSH ACOT = ACOT ACOTH = ACOTH AGGREGATE = TOPLAMA ARABIC = ARAP ASIN = ASİN ASINH = ASİNH ATAN = ATAN ATAN2 = ATAN2 ATANH = ATANH BASE = TABAN CEILING.MATH = TAVANAYUVARLA.MATEMATİK CEILING.PRECISE = TAVANAYUVARLA.DUYARLI COMBIN = KOMBİNASYON COMBINA = KOMBİNASYONA COS = COS COSH = COSH COT = COT COTH = COTH CSC = CSC CSCH = CSCH DECIMAL = ONDALIK DEGREES = DERECE ECMA.CEILING = ECMA.TAVAN EVEN = ÇİFT EXP = ÜS FACT = ÇARPINIM FACTDOUBLE = ÇİFTFAKTÖR FLOOR.MATH = TABANAYUVARLA.MATEMATİK FLOOR.PRECISE = TABANAYUVARLA.DUYARLI GCD = OBEB INT = TAMSAYI ISO.CEILING = ISO.TAVAN LCM = OKEK LN = LN LOG = LOG LOG10 = LOG10 MDETERM = DETERMİNANT MINVERSE = DİZEY_TERS MMULT = DÇARP MOD = MOD MROUND = KYUVARLA MULTINOMIAL = ÇOKTERİMLİ MUNIT = BİRİMMATRİS ODD = TEK PI = Pİ POWER = KUVVET PRODUCT = ÇARPIM QUOTIENT = BÖLÜM RADIANS = RADYAN RAND = S_SAYI_ÜRET RANDBETWEEN = RASTGELEARADA ROMAN = ROMEN ROUND = YUVARLA ROUNDBAHTDOWN = BAHTAŞAĞIYUVARLA ROUNDBAHTUP = BAHTYUKARIYUVARLA ROUNDDOWN = AŞAĞIYUVARLA ROUNDUP = YUKARIYUVARLA SEC = SEC SECH = SECH SERIESSUM = SERİTOPLA SIGN = İŞARET SIN = SİN SINH = SİNH SQRT = KAREKÖK SQRTPI = KAREKÖKPİ SUBTOTAL = ALTTOPLAM SUM = TOPLA SUMIF = ETOPLA SUMIFS = ÇOKETOPLA SUMPRODUCT = TOPLA.ÇARPIM SUMSQ = TOPKARE SUMX2MY2 = TOPX2EY2 SUMX2PY2 = TOPX2AY2 SUMXMY2 = TOPXEY2 TAN = TAN TANH = TANH TRUNC = NSAT ## ## İstatistik işlevleri (Statistical Functions) ## AVEDEV = ORTSAP AVERAGE = ORTALAMA AVERAGEA = ORTALAMAA AVERAGEIF = EĞERORTALAMA AVERAGEIFS = ÇOKEĞERORTALAMA BETA.DIST = BETA.DAĞ BETA.INV = BETA.TERS BINOM.DIST = BİNOM.DAĞ BINOM.DIST.RANGE = BİNOM.DAĞ.ARALIK BINOM.INV = BİNOM.TERS CHISQ.DIST = KİKARE.DAĞ CHISQ.DIST.RT = KİKARE.DAĞ.SAĞK CHISQ.INV = KİKARE.TERS CHISQ.INV.RT = KİKARE.TERS.SAĞK CHISQ.TEST = KİKARE.TEST CONFIDENCE.NORM = GÜVENİLİRLİK.NORM CONFIDENCE.T = GÜVENİLİRLİK.T CORREL = KORELASYON COUNT = BAĞ_DEĞ_SAY COUNTA = BAĞ_DEĞ_DOLU_SAY COUNTBLANK = BOŞLUKSAY COUNTIF = EĞERSAY COUNTIFS = ÇOKEĞERSAY COVARIANCE.P = KOVARYANS.P COVARIANCE.S = KOVARYANS.S DEVSQ = SAPKARE EXPON.DIST = ÜSTEL.DAĞ F.DIST = F.DAĞ F.DIST.RT = F.DAĞ.SAĞK F.INV = F.TERS F.INV.RT = F.TERS.SAĞK F.TEST = F.TEST FISHER = FISHER FISHERINV = FISHERTERS FORECAST.ETS = TAHMİN.ETS FORECAST.ETS.CONFINT = TAHMİN.ETS.GVNARAL FORECAST.ETS.SEASONALITY = TAHMİN.ETS.MEVSİMSELLİK FORECAST.ETS.STAT = TAHMİN.ETS.İSTAT FORECAST.LINEAR = TAHMİN.DOĞRUSAL FREQUENCY = SIKLIK GAMMA = GAMA GAMMA.DIST = GAMA.DAĞ GAMMA.INV = GAMA.TERS GAMMALN = GAMALN GAMMALN.PRECISE = GAMALN.DUYARLI GAUSS = GAUSS GEOMEAN = GEOORT GROWTH = BÜYÜME HARMEAN = HARORT HYPGEOM.DIST = HİPERGEOM.DAĞ INTERCEPT = KESMENOKTASI KURT = BASIKLIK LARGE = BÜYÜK LINEST = DOT LOGEST = LOT LOGNORM.DIST = LOGNORM.DAĞ LOGNORM.INV = LOGNORM.TERS MAX = MAK MAXA = MAKA MAXIFS = ÇOKEĞERMAK MEDIAN = ORTANCA MIN = MİN MINA = MİNA MINIFS = ÇOKEĞERMİN MODE.MULT = ENÇOK_OLAN.ÇOK MODE.SNGL = ENÇOK_OLAN.TEK NEGBINOM.DIST = NEGBİNOM.DAĞ NORM.DIST = NORM.DAĞ NORM.INV = NORM.TERS NORM.S.DIST = NORM.S.DAĞ NORM.S.INV = NORM.S.TERS PEARSON = PEARSON PERCENTILE.EXC = YÜZDEBİRLİK.HRC PERCENTILE.INC = YÜZDEBİRLİK.DHL PERCENTRANK.EXC = YÜZDERANK.HRC PERCENTRANK.INC = YÜZDERANK.DHL PERMUT = PERMÜTASYON PERMUTATIONA = PERMÜTASYONA PHI = PHI POISSON.DIST = POISSON.DAĞ PROB = OLASILIK QUARTILE.EXC = DÖRTTEBİRLİK.HRC QUARTILE.INC = DÖRTTEBİRLİK.DHL RANK.AVG = RANK.ORT RANK.EQ = RANK.EŞİT RSQ = RKARE SKEW = ÇARPIKLIK SKEW.P = ÇARPIKLIK.P SLOPE = EĞİM SMALL = KÜÇÜK STANDARDIZE = STANDARTLAŞTIRMA STDEV.P = STDSAPMA.P STDEV.S = STDSAPMA.S STDEVA = STDSAPMAA STDEVPA = STDSAPMASA STEYX = STHYX T.DIST = T.DAĞ T.DIST.2T = T.DAĞ.2K T.DIST.RT = T.DAĞ.SAĞK T.INV = T.TERS T.INV.2T = T.TERS.2K T.TEST = T.TEST TREND = EĞİLİM TRIMMEAN = KIRPORTALAMA VAR.P = VAR.P VAR.S = VAR.S VARA = VARA VARPA = VARSA WEIBULL.DIST = WEIBULL.DAĞ Z.TEST = Z.TEST ## ## Metin işlevleri (Text Functions) ## BAHTTEXT = BAHTMETİN CHAR = DAMGA CLEAN = TEMİZ CODE = KOD CONCAT = ARALIKBİRLEŞTİR DOLLAR = LİRA EXACT = ÖZDEŞ FIND = BUL FIXED = SAYIDÜZENLE ISTHAIDIGIT = TAYRAKAMIYSA LEFT = SOLDAN LEN = UZUNLUK LOWER = KÜÇÜKHARF MID = PARÇAAL NUMBERSTRING = SAYIDİZİ NUMBERVALUE = SAYIDEĞERİ PHONETIC = SES PROPER = YAZIM.DÜZENİ REPLACE = DEĞİŞTİR REPT = YİNELE RIGHT = SAĞDAN SEARCH = MBUL SUBSTITUTE = YERİNEKOY T = M TEXT = METNEÇEVİR TEXTJOIN = METİNBİRLEŞTİR THAIDIGIT = TAYRAKAM THAINUMSOUND = TAYSAYISES THAINUMSTRING = TAYSAYIDİZE THAISTRINGLENGTH = TAYDİZEUZUNLUĞU TRIM = KIRP UNICHAR = UNICODEKARAKTERİ UNICODE = UNICODE UPPER = BÜYÜKHARF VALUE = SAYIYAÇEVİR ## ## Metin işlevleri (Web Functions) ## ENCODEURL = URLKODLA FILTERXML = XMLFİLTRELE WEBSERVICE = WEBHİZMETİ ## ## Uyumluluk işlevleri (Compatibility Functions) ## BETADIST = BETADAĞ BETAINV = BETATERS BINOMDIST = BİNOMDAĞ CEILING = TAVANAYUVARLA CHIDIST = KİKAREDAĞ CHIINV = KİKARETERS CHITEST = KİKARETEST CONCATENATE = BİRLEŞTİR CONFIDENCE = GÜVENİRLİK COVAR = KOVARYANS CRITBINOM = KRİTİKBİNOM EXPONDIST = ÜSTELDAĞ FDIST = FDAĞ FINV = FTERS FLOOR = TABANAYUVARLA FORECAST = TAHMİN FTEST = FTEST GAMMADIST = GAMADAĞ GAMMAINV = GAMATERS HYPGEOMDIST = HİPERGEOMDAĞ LOGINV = LOGTERS LOGNORMDIST = LOGNORMDAĞ MODE = ENÇOK_OLAN NEGBINOMDIST = NEGBİNOMDAĞ NORMDIST = NORMDAĞ NORMINV = NORMTERS NORMSDIST = NORMSDAĞ NORMSINV = NORMSTERS PERCENTILE = YÜZDEBİRLİK PERCENTRANK = YÜZDERANK POISSON = POISSON QUARTILE = DÖRTTEBİRLİK RANK = RANK STDEV = STDSAPMA STDEVP = STDSAPMAS TDIST = TDAĞ TINV = TTERS TTEST = TTEST VAR = VAR VARP = VARS WEIBULL = WEIBULL ZTEST = ZTEST phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config000064400000000512152427537250020615 0ustar00############################################################ ## ## PhpSpreadsheet - locale settings ## ## Türkçe (Turkish) ## ############################################################ ArgumentSeparator = ; ## ## Error Codes ## NULL = #BOŞ! DIV0 = #SAYI/0! VALUE = #DEĞER! REF = #BAŞV! NAME = #AD? NUM = #SAYI! NA = #YOK phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions000064400000024714152427537250021354 0ustar00############################################################ ## ## 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/config000064400000000516152427537250020603 0ustar00############################################################ ## ## 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/cs/functions000064400000022455152427537250021352 0ustar00############################################################ ## ## 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/config000064400000000535152427537250020602 0ustar00############################################################ ## ## 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/en/uk/config000064400000000107152427537250021211 0ustar00## ## PhpSpreadsheet ## ## ## (For future use) ## currencySymbol = £ phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions000064400000025073152427537250021360 0ustar00############################################################ ## ## 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/config000064400000000531152427537250020605 0ustar00############################################################ ## ## 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/da/functions000064400000024441152427537250021326 0ustar00############################################################ ## ## PhpSpreadsheet - function name translations ## ## Dansk (Danish) ## ############################################################ ## ## Kubefunktioner (Cube Functions) ## CUBEKPIMEMBER = KUBE.KPI.MEDLEM CUBEMEMBER = KUBEMEDLEM CUBEMEMBERPROPERTY = KUBEMEDLEM.EGENSKAB CUBERANKEDMEMBER = KUBERANGERET.MEDLEM CUBESET = KUBESÆT CUBESETCOUNT = KUBESÆT.ANTAL CUBEVALUE = KUBEVÆRDI ## ## Databasefunktioner (Database Functions) ## DAVERAGE = DMIDDEL DCOUNT = DTÆL DCOUNTA = DTÆLV DGET = DHENT DMAX = DMAKS DMIN = DMIN DPRODUCT = DPRODUKT DSTDEV = DSTDAFV DSTDEVP = DSTDAFVP DSUM = DSUM DVAR = DVARIANS DVARP = DVARIANSP ## ## Dato- og klokkeslætfunktioner (Date & Time Functions) ## DATE = DATO DATEDIF = DATO.FORSKEL DATESTRING = DATOSTRENG DATEVALUE = DATOVÆRDI DAY = DAG DAYS = DAGE DAYS360 = DAGE360 EDATE = EDATO EOMONTH = SLUT.PÅ.MÅNED HOUR = TIME ISOWEEKNUM = ISOUGE.NR MINUTE = MINUT MONTH = MÅNED NETWORKDAYS = ANTAL.ARBEJDSDAGE NETWORKDAYS.INTL = ANTAL.ARBEJDSDAGE.INTL NOW = NU SECOND = SEKUND THAIDAYOFWEEK = THAILANDSKUGEDAG THAIMONTHOFYEAR = THAILANDSKMÅNED THAIYEAR = THAILANDSKÅR TIME = TID TIMEVALUE = TIDSVÆRDI TODAY = IDAG WEEKDAY = UGEDAG WEEKNUM = UGE.NR WORKDAY = ARBEJDSDAG WORKDAY.INTL = ARBEJDSDAG.INTL YEAR = ÅR YEARFRAC = ÅR.BRØK ## ## Tekniske funktioner (Engineering Functions) ## BESSELI = BESSELI BESSELJ = BESSELJ BESSELK = BESSELK BESSELY = BESSELY BIN2DEC = BIN.TIL.DEC BIN2HEX = BIN.TIL.HEX BIN2OCT = BIN.TIL.OKT BITAND = BITOG BITLSHIFT = BITLSKIFT BITOR = BITELLER BITRSHIFT = BITRSKIFT BITXOR = BITXELLER COMPLEX = KOMPLEKS CONVERT = KONVERTER DEC2BIN = DEC.TIL.BIN DEC2HEX = DEC.TIL.HEX DEC2OCT = DEC.TIL.OKT DELTA = DELTA ERF = FEJLFUNK ERF.PRECISE = ERF.PRECISE ERFC = FEJLFUNK.KOMP ERFC.PRECISE = ERFC.PRECISE GESTEP = GETRIN HEX2BIN = HEX.TIL.BIN HEX2DEC = HEX.TIL.DEC HEX2OCT = HEX.TIL.OKT IMABS = IMAGABS IMAGINARY = IMAGINÆR IMARGUMENT = IMAGARGUMENT IMCONJUGATE = IMAGKONJUGERE IMCOS = IMAGCOS IMCOSH = IMAGCOSH IMCOT = IMAGCOT IMCSC = IMAGCSC IMCSCH = IMAGCSCH IMDIV = IMAGDIV IMEXP = IMAGEKSP IMLN = IMAGLN IMLOG10 = IMAGLOG10 IMLOG2 = IMAGLOG2 IMPOWER = IMAGPOTENS IMPRODUCT = IMAGPRODUKT IMREAL = IMAGREELT IMSEC = IMAGSEC IMSECH = IMAGSECH IMSIN = IMAGSIN IMSINH = IMAGSINH IMSQRT = IMAGKVROD IMSUB = IMAGSUB IMSUM = IMAGSUM IMTAN = IMAGTAN OCT2BIN = OKT.TIL.BIN OCT2DEC = OKT.TIL.DEC OCT2HEX = OKT.TIL.HEX ## ## Finansielle funktioner (Financial Functions) ## ACCRINT = PÅLØBRENTE ACCRINTM = PÅLØBRENTE.UDLØB AMORDEGRC = AMORDEGRC AMORLINC = AMORLINC COUPDAYBS = KUPONDAGE.SA COUPDAYS = KUPONDAGE.A COUPDAYSNC = KUPONDAGE.ANK COUPNCD = KUPONDAG.NÆSTE COUPNUM = KUPONBETALINGER COUPPCD = KUPONDAG.FORRIGE CUMIPMT = AKKUM.RENTE CUMPRINC = AKKUM.HOVEDSTOL DB = DB DDB = DSA DISC = DISKONTO DOLLARDE = KR.DECIMAL DOLLARFR = KR.BRØK DURATION = VARIGHED EFFECT = EFFEKTIV.RENTE FV = FV FVSCHEDULE = FVTABEL INTRATE = RENTEFOD IPMT = R.YDELSE IRR = IA ISPMT = ISPMT MDURATION = MVARIGHED MIRR = MIA NOMINAL = NOMINEL NPER = NPER NPV = NUTIDSVÆRDI ODDFPRICE = ULIGE.KURS.PÅLYDENDE ODDFYIELD = ULIGE.FØRSTE.AFKAST ODDLPRICE = ULIGE.SIDSTE.KURS ODDLYIELD = ULIGE.SIDSTE.AFKAST PDURATION = PVARIGHED PMT = YDELSE PPMT = H.YDELSE PRICE = KURS PRICEDISC = KURS.DISKONTO PRICEMAT = KURS.UDLØB PV = NV RATE = RENTE RECEIVED = MODTAGET.VED.UDLØB RRI = RRI SLN = LA SYD = ÅRSAFSKRIVNING TBILLEQ = STATSOBLIGATION TBILLPRICE = STATSOBLIGATION.KURS TBILLYIELD = STATSOBLIGATION.AFKAST VDB = VSA XIRR = INTERN.RENTE XNPV = NETTO.NUTIDSVÆRDI YIELD = AFKAST YIELDDISC = AFKAST.DISKONTO YIELDMAT = AFKAST.UDLØBSDATO ## ## Informationsfunktioner (Information Functions) ## CELL = CELLE ERROR.TYPE = FEJLTYPE INFO = INFO ISBLANK = ER.TOM ISERR = ER.FJL ISERROR = ER.FEJL ISEVEN = ER.LIGE ISFORMULA = ER.FORMEL ISLOGICAL = ER.LOGISK ISNA = ER.IKKE.TILGÆNGELIG ISNONTEXT = ER.IKKE.TEKST ISNUMBER = ER.TAL ISODD = ER.ULIGE ISREF = ER.REFERENCE ISTEXT = ER.TEKST N = TAL NA = IKKE.TILGÆNGELIG SHEET = ARK SHEETS = ARK.FLERE TYPE = VÆRDITYPE ## ## Logiske funktioner (Logical Functions) ## AND = OG FALSE = FALSK IF = HVIS IFERROR = HVIS.FEJL IFNA = HVISIT IFS = HVISER NOT = IKKE OR = ELLER SWITCH = SKIFT TRUE = SAND XOR = XELLER ## ## Opslags- og referencefunktioner (Lookup & Reference Functions) ## ADDRESS = ADRESSE AREAS = OMRÅDER CHOOSE = VÆLG COLUMN = KOLONNE COLUMNS = KOLONNER FORMULATEXT = FORMELTEKST GETPIVOTDATA = GETPIVOTDATA HLOOKUP = VOPSLAG HYPERLINK = HYPERLINK INDEX = INDEKS INDIRECT = INDIREKTE LOOKUP = SLÅ.OP MATCH = SAMMENLIGN OFFSET = FORSKYDNING ROW = RÆKKE ROWS = RÆKKER RTD = RTD TRANSPOSE = TRANSPONER VLOOKUP = LOPSLAG *RC = RK ## ## Matematiske og trigonometriske funktioner (Math & Trig Functions) ## ABS = ABS ACOS = ARCCOS ACOSH = ARCCOSH ACOT = ARCCOT ACOTH = ARCCOTH AGGREGATE = SAMLING ARABIC = ARABISK ASIN = ARCSIN ASINH = ARCSINH ATAN = ARCTAN ATAN2 = ARCTAN2 ATANH = ARCTANH BASE = BASIS CEILING.MATH = LOFT.MAT CEILING.PRECISE = LOFT.PRECISE COMBIN = KOMBIN COMBINA = KOMBINA COS = COS COSH = COSH COT = COT COTH = COTH CSC = CSC CSCH = CSCH DECIMAL = DECIMAL DEGREES = GRADER ECMA.CEILING = ECMA.LOFT EVEN = LIGE EXP = EKSP FACT = FAKULTET FACTDOUBLE = DOBBELT.FAKULTET FLOOR.MATH = AFRUND.BUND.MAT FLOOR.PRECISE = AFRUND.GULV.PRECISE GCD = STØRSTE.FÆLLES.DIVISOR INT = HELTAL ISO.CEILING = ISO.LOFT LCM = MINDSTE.FÆLLES.MULTIPLUM LN = LN LOG = LOG LOG10 = LOG10 MDETERM = MDETERM MINVERSE = MINVERT MMULT = MPRODUKT MOD = REST MROUND = MAFRUND MULTINOMIAL = MULTINOMIAL MUNIT = MENHED ODD = ULIGE PI = PI POWER = POTENS PRODUCT = PRODUKT QUOTIENT = KVOTIENT RADIANS = RADIANER RAND = SLUMP RANDBETWEEN = SLUMPMELLEM ROMAN = ROMERTAL ROUND = AFRUND ROUNDBAHTDOWN = RUNDBAHTNED ROUNDBAHTUP = RUNDBAHTOP ROUNDDOWN = RUND.NED ROUNDUP = RUND.OP SEC = SEC SECH = SECH SERIESSUM = SERIESUM SIGN = FORTEGN SIN = SIN SINH = SINH SQRT = KVROD SQRTPI = KVRODPI SUBTOTAL = SUBTOTAL SUM = SUM SUMIF = SUM.HVIS SUMIFS = SUM.HVISER SUMPRODUCT = SUMPRODUKT SUMSQ = SUMKV SUMX2MY2 = SUMX2MY2 SUMX2PY2 = SUMX2PY2 SUMXMY2 = SUMXMY2 TAN = TAN TANH = TANH TRUNC = AFKORT ## ## Statistiske funktioner (Statistical Functions) ## AVEDEV = MAD AVERAGE = MIDDEL AVERAGEA = MIDDELV AVERAGEIF = MIDDEL.HVIS AVERAGEIFS = MIDDEL.HVISER BETA.DIST = BETA.FORDELING BETA.INV = BETA.INV BINOM.DIST = BINOMIAL.FORDELING BINOM.DIST.RANGE = BINOMIAL.DIST.INTERVAL BINOM.INV = BINOMIAL.INV CHISQ.DIST = CHI2.FORDELING CHISQ.DIST.RT = CHI2.FORD.RT CHISQ.INV = CHI2.INV CHISQ.INV.RT = CHI2.INV.RT CHISQ.TEST = CHI2.TEST CONFIDENCE.NORM = KONFIDENS.NORM CONFIDENCE.T = KONFIDENST CORREL = KORRELATION COUNT = TÆL COUNTA = TÆLV COUNTBLANK = ANTAL.BLANKE COUNTIF = TÆL.HVIS COUNTIFS = TÆL.HVISER COVARIANCE.P = KOVARIANS.P COVARIANCE.S = KOVARIANS.S DEVSQ = SAK EXPON.DIST = EKSP.FORDELING F.DIST = F.FORDELING F.DIST.RT = F.FORDELING.RT F.INV = F.INV F.INV.RT = F.INV.RT F.TEST = F.TEST FISHER = FISHER FISHERINV = FISHERINV FORECAST.ETS = PROGNOSE.ETS FORECAST.ETS.CONFINT = PROGNOSE.ETS.CONFINT FORECAST.ETS.SEASONALITY = PROGNOSE.ETS.SÆSONUDSVING 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.PRECISE GAUSS = GAUSS GEOMEAN = GEOMIDDELVÆRDI GROWTH = FORØGELSE HARMEAN = HARMIDDELVÆRDI HYPGEOM.DIST = HYPGEO.FORDELING INTERCEPT = SKÆRING KURT = TOPSTEJL LARGE = STØRSTE LINEST = LINREGR LOGEST = LOGREGR LOGNORM.DIST = LOGNORM.FORDELING LOGNORM.INV = LOGNORM.INV MAX = MAKS MAXA = MAKSV MAXIFS = MAKSHVISER MEDIAN = MEDIAN MIN = MIN MINA = MINV MINIFS = MINHVISER MODE.MULT = HYPPIGST.FLERE MODE.SNGL = HYPPIGST.ENKELT NEGBINOM.DIST = NEGBINOM.FORDELING NORM.DIST = NORMAL.FORDELING NORM.INV = NORM.INV NORM.S.DIST = STANDARD.NORM.FORDELING NORM.S.INV = STANDARD.NORM.INV PEARSON = PEARSON PERCENTILE.EXC = FRAKTIL.UDELAD PERCENTILE.INC = FRAKTIL.MEDTAG PERCENTRANK.EXC = PROCENTPLADS.UDELAD PERCENTRANK.INC = PROCENTPLADS.MEDTAG PERMUT = PERMUT PERMUTATIONA = PERMUTATIONA PHI = PHI POISSON.DIST = POISSON.FORDELING PROB = SANDSYNLIGHED QUARTILE.EXC = KVARTIL.UDELAD QUARTILE.INC = KVARTIL.MEDTAG RANK.AVG = PLADS.GNSN RANK.EQ = PLADS.LIGE RSQ = FORKLARINGSGRAD SKEW = SKÆVHED SKEW.P = SKÆVHED.P SLOPE = STIGNING SMALL = MINDSTE STANDARDIZE = STANDARDISER STDEV.P = STDAFV.P STDEV.S = STDAFV.S STDEVA = STDAFVV STDEVPA = STDAFVPV STEYX = STFYX T.DIST = T.FORDELING T.DIST.2T = T.FORDELING.2T T.DIST.RT = T.FORDELING.RT T.INV = T.INV T.INV.2T = T.INV.2T T.TEST = T.TEST TREND = TENDENS TRIMMEAN = TRIMMIDDELVÆRDI VAR.P = VARIANS.P VAR.S = VARIANS.S VARA = VARIANSV VARPA = VARIANSPV WEIBULL.DIST = WEIBULL.FORDELING Z.TEST = Z.TEST ## ## Tekstfunktioner (Text Functions) ## BAHTTEXT = BAHTTEKST CHAR = TEGN CLEAN = RENS CODE = KODE CONCAT = CONCAT DOLLAR = KR EXACT = EKSAKT FIND = FIND FIXED = FAST ISTHAIDIGIT = ERTHAILANDSKCIFFER LEFT = VENSTRE LEN = LÆNGDE LOWER = SMÅ.BOGSTAVER MID = MIDT NUMBERSTRING = TALSTRENG NUMBERVALUE = TALVÆRDI PHONETIC = FONETISK PROPER = STORT.FORBOGSTAV REPLACE = ERSTAT REPT = GENTAG RIGHT = HØJRE SEARCH = SØG SUBSTITUTE = UDSKIFT T = T TEXT = TEKST TEXTJOIN = TEKST.KOMBINER THAIDIGIT = THAILANDSKCIFFER THAINUMSOUND = THAILANDSKNUMLYD THAINUMSTRING = THAILANDSKNUMSTRENG THAISTRINGLENGTH = THAILANDSKSTRENGLÆNGDE TRIM = FJERN.OVERFLØDIGE.BLANKE UNICHAR = UNICHAR UNICODE = UNICODE UPPER = STORE.BOGSTAVER VALUE = VÆRDI ## ## Webfunktioner (Web Functions) ## ENCODEURL = KODNINGSURL FILTERXML = FILTRERXML WEBSERVICE = WEBTJENESTE ## ## Kompatibilitetsfunktioner (Compatibility Functions) ## BETADIST = BETAFORDELING BETAINV = BETAINV BINOMDIST = BINOMIALFORDELING CEILING = AFRUND.LOFT CHIDIST = CHIFORDELING CHIINV = CHIINV CHITEST = CHITEST CONCATENATE = SAMMENKÆDNING CONFIDENCE = KONFIDENSINTERVAL COVAR = KOVARIANS CRITBINOM = KRITBINOM EXPONDIST = EKSPFORDELING FDIST = FFORDELING FINV = FINV FLOOR = AFRUND.GULV FORECAST = PROGNOSE FTEST = FTEST GAMMADIST = GAMMAFORDELING GAMMAINV = GAMMAINV HYPGEOMDIST = HYPGEOFORDELING LOGINV = LOGINV LOGNORMDIST = LOGNORMFORDELING MODE = HYPPIGST NEGBINOMDIST = NEGBINOMFORDELING NORMDIST = NORMFORDELING NORMINV = NORMINV NORMSDIST = STANDARDNORMFORDELING NORMSINV = STANDARDNORMINV PERCENTILE = FRAKTIL PERCENTRANK = PROCENTPLADS POISSON = POISSON QUARTILE = KVARTIL RANK = PLADS STDEV = STDAFV STDEVP = STDAFVP TDIST = TFORDELING TINV = TINV TTEST = TTEST VAR = VARIANS VARP = VARIANSP WEIBULL = WEIBULL ZTEST = ZTEST phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config000064400000000467152427537250020565 0ustar00############################################################ ## ## PhpSpreadsheet - locale settings ## ## Dansk (Danish) ## ############################################################ ArgumentSeparator = ; ## ## Error Codes ## NULL = #NUL! DIV0 VALUE = #VÆRDI! REF = #REFERENCE! NAME = #NAVN? NUM NA = #I/T phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions000064400000024377152427537250021363 0ustar00############################################################ ## ## PhpSpreadsheet - function name translations ## ## Nederlands (Dutch) ## ############################################################ ## ## Kubusfuncties (Cube Functions) ## CUBEKPIMEMBER = KUBUSKPILID CUBEMEMBER = KUBUSLID CUBEMEMBERPROPERTY = KUBUSLIDEIGENSCHAP CUBERANKEDMEMBER = KUBUSGERANGSCHIKTLID CUBESET = KUBUSSET CUBESETCOUNT = KUBUSSETAANTAL CUBEVALUE = KUBUSWAARDE ## ## Databasefuncties (Database Functions) ## DAVERAGE = DBGEMIDDELDE DCOUNT = DBAANTAL DCOUNTA = DBAANTALC DGET = DBLEZEN DMAX = DBMAX DMIN = DBMIN DPRODUCT = DBPRODUCT DSTDEV = DBSTDEV DSTDEVP = DBSTDEVP DSUM = DBSOM DVAR = DBVAR DVARP = DBVARP ## ## Datum- en tijdfuncties (Date & Time Functions) ## DATE = DATUM DATESTRING = DATUMNOTATIE DATEVALUE = DATUMWAARDE DAY = DAG DAYS = DAGEN DAYS360 = DAGEN360 EDATE = ZELFDE.DAG EOMONTH = LAATSTE.DAG HOUR = UUR ISOWEEKNUM = ISO.WEEKNUMMER MINUTE = MINUUT MONTH = MAAND NETWORKDAYS = NETTO.WERKDAGEN NETWORKDAYS.INTL = NETWERKDAGEN.INTL NOW = NU SECOND = SECONDE THAIDAYOFWEEK = THAIS.WEEKDAG THAIMONTHOFYEAR = THAIS.MAAND.VAN.JAAR THAIYEAR = THAIS.JAAR TIME = TIJD TIMEVALUE = TIJDWAARDE TODAY = VANDAAG WEEKDAY = WEEKDAG WEEKNUM = WEEKNUMMER WORKDAY = WERKDAG WORKDAY.INTL = WERKDAG.INTL YEAR = JAAR YEARFRAC = JAAR.DEEL ## ## Technische functies (Engineering Functions) ## BESSELI = BESSEL.I BESSELJ = BESSEL.J BESSELK = BESSEL.K BESSELY = BESSEL.Y BIN2DEC = BIN.N.DEC BIN2HEX = BIN.N.HEX BIN2OCT = BIN.N.OCT BITAND = BIT.EN BITLSHIFT = BIT.VERSCHUIF.LINKS BITOR = BIT.OF BITRSHIFT = BIT.VERSCHUIF.RECHTS BITXOR = BIT.EX.OF COMPLEX = COMPLEX CONVERT = CONVERTEREN DEC2BIN = DEC.N.BIN DEC2HEX = DEC.N.HEX DEC2OCT = DEC.N.OCT DELTA = DELTA ERF = FOUTFUNCTIE ERF.PRECISE = FOUTFUNCTIE.NAUWKEURIG ERFC = FOUT.COMPLEMENT ERFC.PRECISE = FOUT.COMPLEMENT.NAUWKEURIG GESTEP = GROTER.DAN HEX2BIN = HEX.N.BIN HEX2DEC = HEX.N.DEC HEX2OCT = HEX.N.OCT IMABS = C.ABS IMAGINARY = C.IM.DEEL IMARGUMENT = C.ARGUMENT IMCONJUGATE = C.TOEGEVOEGD IMCOS = C.COS IMCOSH = C.COSH IMCOT = C.COT IMCSC = C.COSEC IMCSCH = C.COSECH IMDIV = C.QUOTIENT IMEXP = C.EXP IMLN = C.LN IMLOG10 = C.LOG10 IMLOG2 = C.LOG2 IMPOWER = C.MACHT IMPRODUCT = C.PRODUCT IMREAL = C.REEEL.DEEL IMSEC = C.SEC IMSECH = C.SECH IMSIN = C.SIN IMSINH = C.SINH IMSQRT = C.WORTEL IMSUB = C.VERSCHIL IMSUM = C.SOM IMTAN = C.TAN OCT2BIN = OCT.N.BIN OCT2DEC = OCT.N.DEC OCT2HEX = OCT.N.HEX ## ## Financiële functies (Financial Functions) ## ACCRINT = SAMENG.RENTE ACCRINTM = SAMENG.RENTE.V AMORDEGRC = AMORDEGRC AMORLINC = AMORLINC COUPDAYBS = COUP.DAGEN.BB COUPDAYS = COUP.DAGEN COUPDAYSNC = COUP.DAGEN.VV COUPNCD = COUP.DATUM.NB COUPNUM = COUP.AANTAL COUPPCD = COUP.DATUM.VB CUMIPMT = CUM.RENTE CUMPRINC = CUM.HOOFDSOM DB = DB DDB = DDB DISC = DISCONTO DOLLARDE = EURO.DE DOLLARFR = EURO.BR DURATION = DUUR EFFECT = EFFECT.RENTE FV = TW FVSCHEDULE = TOEK.WAARDE2 INTRATE = RENTEPERCENTAGE IPMT = IBET IRR = IR ISPMT = ISBET MDURATION = AANG.DUUR MIRR = GIR NOMINAL = NOMINALE.RENTE NPER = NPER NPV = NHW ODDFPRICE = AFW.ET.PRIJS ODDFYIELD = AFW.ET.REND ODDLPRICE = AFW.LT.PRIJS ODDLYIELD = AFW.LT.REND PDURATION = PDUUR PMT = BET PPMT = PBET PRICE = PRIJS.NOM PRICEDISC = PRIJS.DISCONTO PRICEMAT = PRIJS.VERVALDAG PV = HW RATE = RENTE RECEIVED = OPBRENGST RRI = RRI SLN = LIN.AFSCHR SYD = SYD TBILLEQ = SCHATK.OBL TBILLPRICE = SCHATK.PRIJS TBILLYIELD = SCHATK.REND VDB = VDB XIRR = IR.SCHEMA XNPV = NHW2 YIELD = RENDEMENT YIELDDISC = REND.DISCONTO YIELDMAT = REND.VERVAL ## ## Informatiefuncties (Information Functions) ## CELL = CEL ERROR.TYPE = TYPE.FOUT INFO = INFO ISBLANK = ISLEEG ISERR = ISFOUT2 ISERROR = ISFOUT ISEVEN = IS.EVEN ISFORMULA = ISFORMULE ISLOGICAL = ISLOGISCH ISNA = ISNB ISNONTEXT = ISGEENTEKST ISNUMBER = ISGETAL ISODD = IS.ONEVEN ISREF = ISVERWIJZING ISTEXT = ISTEKST N = N NA = NB SHEET = BLAD SHEETS = BLADEN TYPE = TYPE ## ## Logische functies (Logical Functions) ## AND = EN FALSE = ONWAAR IF = ALS IFERROR = ALS.FOUT IFNA = ALS.NB IFS = ALS.VOORWAARDEN NOT = NIET OR = OF SWITCH = SCHAKELEN TRUE = WAAR XOR = EX.OF ## ## Zoek- en verwijzingsfuncties (Lookup & Reference Functions) ## ADDRESS = ADRES AREAS = BEREIKEN CHOOSE = KIEZEN COLUMN = KOLOM COLUMNS = KOLOMMEN FORMULATEXT = FORMULETEKST GETPIVOTDATA = DRAAITABEL.OPHALEN HLOOKUP = HORIZ.ZOEKEN HYPERLINK = HYPERLINK INDEX = INDEX INDIRECT = INDIRECT LOOKUP = ZOEKEN MATCH = VERGELIJKEN OFFSET = VERSCHUIVING ROW = RIJ ROWS = RIJEN RTD = RTG TRANSPOSE = TRANSPONEREN VLOOKUP = VERT.ZOEKEN *RC = RK ## ## Wiskundige en trigonometrische functies (Math & Trig Functions) ## ABS = ABS ACOS = BOOGCOS ACOSH = BOOGCOSH ACOT = BOOGCOT ACOTH = BOOGCOTH AGGREGATE = AGGREGAAT ARABIC = ARABISCH ASIN = BOOGSIN ASINH = BOOGSINH ATAN = BOOGTAN ATAN2 = BOOGTAN2 ATANH = BOOGTANH BASE = BASIS CEILING.MATH = AFRONDEN.BOVEN.WISK CEILING.PRECISE = AFRONDEN.BOVEN.NAUWKEURIG COMBIN = COMBINATIES COMBINA = COMBIN.A COS = COS COSH = COSH COT = COT COTH = COTH CSC = COSEC CSCH = COSECH DECIMAL = DECIMAAL DEGREES = GRADEN ECMA.CEILING = ECMA.AFRONDEN.BOVEN EVEN = EVEN EXP = EXP FACT = FACULTEIT FACTDOUBLE = DUBBELE.FACULTEIT FLOOR.MATH = AFRONDEN.BENEDEN.WISK FLOOR.PRECISE = AFRONDEN.BENEDEN.NAUWKEURIG GCD = GGD INT = INTEGER ISO.CEILING = ISO.AFRONDEN.BOVEN LCM = KGV LN = LN LOG = LOG LOG10 = LOG10 MDETERM = DETERMINANTMAT MINVERSE = INVERSEMAT MMULT = PRODUCTMAT MOD = REST MROUND = AFRONDEN.N.VEELVOUD MULTINOMIAL = MULTINOMIAAL MUNIT = EENHEIDMAT ODD = ONEVEN PI = PI POWER = MACHT PRODUCT = PRODUCT QUOTIENT = QUOTIENT RADIANS = RADIALEN RAND = ASELECT RANDBETWEEN = ASELECTTUSSEN ROMAN = ROMEINS ROUND = AFRONDEN ROUNDBAHTDOWN = BAHT.AFR.NAAR.BENEDEN ROUNDBAHTUP = BAHT.AFR.NAAR.BOVEN ROUNDDOWN = AFRONDEN.NAAR.BENEDEN ROUNDUP = AFRONDEN.NAAR.BOVEN SEC = SEC SECH = SECH SERIESSUM = SOM.MACHTREEKS SIGN = POS.NEG SIN = SIN SINH = SINH SQRT = WORTEL SQRTPI = WORTEL.PI SUBTOTAL = SUBTOTAAL SUM = SOM SUMIF = SOM.ALS SUMIFS = SOMMEN.ALS SUMPRODUCT = SOMPRODUCT SUMSQ = KWADRATENSOM SUMX2MY2 = SOM.X2MINY2 SUMX2PY2 = SOM.X2PLUSY2 SUMXMY2 = SOM.XMINY.2 TAN = TAN TANH = TANH TRUNC = GEHEEL ## ## Statistische functies (Statistical Functions) ## AVEDEV = GEM.DEVIATIE AVERAGE = GEMIDDELDE AVERAGEA = GEMIDDELDEA AVERAGEIF = GEMIDDELDE.ALS AVERAGEIFS = GEMIDDELDEN.ALS BETA.DIST = BETA.VERD BETA.INV = BETA.INV BINOM.DIST = BINOM.VERD BINOM.DIST.RANGE = BINOM.VERD.BEREIK BINOM.INV = BINOMIALE.INV CHISQ.DIST = CHIKW.VERD CHISQ.DIST.RT = CHIKW.VERD.RECHTS CHISQ.INV = CHIKW.INV CHISQ.INV.RT = CHIKW.INV.RECHTS CHISQ.TEST = CHIKW.TEST CONFIDENCE.NORM = VERTROUWELIJKHEID.NORM CONFIDENCE.T = VERTROUWELIJKHEID.T CORREL = CORRELATIE COUNT = AANTAL COUNTA = AANTALARG COUNTBLANK = AANTAL.LEGE.CELLEN COUNTIF = AANTAL.ALS COUNTIFS = AANTALLEN.ALS COVARIANCE.P = COVARIANTIE.P COVARIANCE.S = COVARIANTIE.S DEVSQ = DEV.KWAD EXPON.DIST = EXPON.VERD.N F.DIST = F.VERD F.DIST.RT = F.VERD.RECHTS F.INV = F.INV F.INV.RT = F.INV.RECHTS F.TEST = F.TEST FISHER = FISHER FISHERINV = FISHER.INV FORECAST.ETS = VOORSPELLEN.ETS FORECAST.ETS.CONFINT = VOORSPELLEN.ETS.CONFINT FORECAST.ETS.SEASONALITY = VOORSPELLEN.ETS.SEASONALITY FORECAST.ETS.STAT = FORECAST.ETS.STAT FORECAST.LINEAR = VOORSPELLEN.LINEAR FREQUENCY = INTERVAL GAMMA = GAMMA GAMMA.DIST = GAMMA.VERD.N GAMMA.INV = GAMMA.INV.N GAMMALN = GAMMA.LN GAMMALN.PRECISE = GAMMA.LN.NAUWKEURIG GAUSS = GAUSS GEOMEAN = MEETK.GEM GROWTH = GROEI HARMEAN = HARM.GEM HYPGEOM.DIST = HYPGEOM.VERD INTERCEPT = SNIJPUNT KURT = KURTOSIS LARGE = GROOTSTE LINEST = LIJNSCH LOGEST = LOGSCH LOGNORM.DIST = LOGNORM.VERD LOGNORM.INV = LOGNORM.INV MAX = MAX MAXA = MAXA MAXIFS = MAX.ALS.VOORWAARDEN MEDIAN = MEDIAAN MIN = MIN MINA = MINA MINIFS = MIN.ALS.VOORWAARDEN MODE.MULT = MODUS.MEERV MODE.SNGL = MODUS.ENKELV NEGBINOM.DIST = NEGBINOM.VERD NORM.DIST = NORM.VERD.N NORM.INV = NORM.INV.N NORM.S.DIST = NORM.S.VERD NORM.S.INV = NORM.S.INV PEARSON = PEARSON PERCENTILE.EXC = PERCENTIEL.EXC PERCENTILE.INC = PERCENTIEL.INC PERCENTRANK.EXC = PROCENTRANG.EXC PERCENTRANK.INC = PROCENTRANG.INC PERMUT = PERMUTATIES PERMUTATIONA = PERMUTATIE.A PHI = PHI POISSON.DIST = POISSON.VERD PROB = KANS QUARTILE.EXC = KWARTIEL.EXC QUARTILE.INC = KWARTIEL.INC RANK.AVG = RANG.GEMIDDELDE RANK.EQ = RANG.GELIJK RSQ = R.KWADRAAT SKEW = SCHEEFHEID SKEW.P = SCHEEFHEID.P SLOPE = RICHTING SMALL = KLEINSTE STANDARDIZE = NORMALISEREN STDEV.P = STDEV.P STDEV.S = STDEV.S STDEVA = STDEVA STDEVPA = STDEVPA STEYX = STAND.FOUT.YX T.DIST = T.DIST T.DIST.2T = T.VERD.2T T.DIST.RT = T.VERD.RECHTS T.INV = T.INV T.INV.2T = T.INV.2T T.TEST = T.TEST TREND = TREND TRIMMEAN = GETRIMD.GEM VAR.P = VAR.P VAR.S = VAR.S VARA = VARA VARPA = VARPA WEIBULL.DIST = WEIBULL.VERD Z.TEST = Z.TEST ## ## Tekstfuncties (Text Functions) ## BAHTTEXT = BAHT.TEKST CHAR = TEKEN CLEAN = WISSEN.CONTROL CODE = CODE CONCAT = TEKST.SAMENV DOLLAR = EURO EXACT = GELIJK FIND = VIND.ALLES FIXED = VAST ISTHAIDIGIT = IS.THAIS.CIJFER LEFT = LINKS LEN = LENGTE LOWER = KLEINE.LETTERS MID = DEEL NUMBERSTRING = GETALNOTATIE NUMBERVALUE = NUMERIEKE.WAARDE PHONETIC = FONETISCH PROPER = BEGINLETTERS REPLACE = VERVANGEN REPT = HERHALING RIGHT = RECHTS SEARCH = VIND.SPEC SUBSTITUTE = SUBSTITUEREN T = T TEXT = TEKST TEXTJOIN = TEKST.COMBINEREN THAIDIGIT = THAIS.CIJFER THAINUMSOUND = THAIS.GETAL.GELUID THAINUMSTRING = THAIS.GETAL.REEKS THAISTRINGLENGTH = THAIS.REEKS.LENGTE TRIM = SPATIES.WISSEN UNICHAR = UNITEKEN UNICODE = UNICODE UPPER = HOOFDLETTERS VALUE = WAARDE ## ## Webfuncties (Web Functions) ## ENCODEURL = URL.CODEREN FILTERXML = XML.FILTEREN WEBSERVICE = WEBSERVICE ## ## Compatibiliteitsfuncties (Compatibility Functions) ## BETADIST = BETAVERD BETAINV = BETAINV BINOMDIST = BINOMIALE.VERD CEILING = AFRONDEN.BOVEN CHIDIST = CHI.KWADRAAT CHIINV = CHI.KWADRAAT.INV CHITEST = CHI.TOETS CONCATENATE = TEKST.SAMENVOEGEN CONFIDENCE = BETROUWBAARHEID COVAR = COVARIANTIE CRITBINOM = CRIT.BINOM EXPONDIST = EXPON.VERD FDIST = F.VERDELING FINV = F.INVERSE FLOOR = AFRONDEN.BENEDEN FORECAST = VOORSPELLEN FTEST = F.TOETS GAMMADIST = GAMMA.VERD GAMMAINV = GAMMA.INV HYPGEOMDIST = HYPERGEO.VERD LOGINV = LOG.NORM.INV LOGNORMDIST = LOG.NORM.VERD MODE = MODUS NEGBINOMDIST = NEG.BINOM.VERD NORMDIST = NORM.VERD NORMINV = NORM.INV NORMSDIST = STAND.NORM.VERD NORMSINV = STAND.NORM.INV PERCENTILE = PERCENTIEL PERCENTRANK = PERCENT.RANG POISSON = POISSON QUARTILE = KWARTIEL RANK = RANG STDEV = STDEV STDEVP = STDEVP TDIST = T.VERD TINV = TINV TTEST = T.TOETS VAR = VAR VARP = VARP WEIBULL = WEIBULL ZTEST = Z.TOETS phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config000064400000000514152427537250020603 0ustar00############################################################ ## ## PhpSpreadsheet - locale settings ## ## Nederlands (Dutch) ## ############################################################ ArgumentSeparator = ; ## ## Error Codes ## NULL = #LEEG! DIV0 = #DEEL/0! VALUE = #WAARDE! REF = #VERW! NAME = #NAAM? NUM = #GETAL! NA = #N/B phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions000064400000024740152427537250021353 0ustar00############################################################ ## ## 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/config000064400000000460152427537250020601 0ustar00############################################################ ## ## 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/functions000064400000023633152427537250021334 0ustar00############################################################ ## ## 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/config000064400000000452152427537250020563 0ustar00############################################################ ## ## 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/functions000064400000024751152427537250021345 0ustar00############################################################ ## ## 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/config000064400000000463152427537250020574 0ustar00############################################################ ## ## 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/Translations.xlsx000064400000324313152427537250022411 0ustar00PK! [Content_Types].xml (Ėˎ@EgQٞLLFi~`,' Hـ{nޖ"f!q="$rUz3qOpÙ(W)Q`f>/K"~+^8J ,զ۞_7NƉWfBPʭ~4r73 T2 01'B^dF(qUƑ1,LO–ܓǿD*uO^hrH^U})w>ޯ"C^*ډo#xO>zT2#J"*BF|LW8𡽭Z<Uтh}lYLa؞vػ}?P6j" ,ml(I:(1ikL0{s]`Br}> ëHK4D2pꦗƉwz e w pYiPK!,t _rels/.rels (N0 ;徦Bh.i7xUm(`{{`FAbǶ:?dxr,\ tLR,GwRDg#b;S\5>TRRQBȣK_* 8=Zy-Ԩy~q 9sS07WR,>")ȇ$ ܤ^B\JC)DRFTq*F,~ at3փ;vl^">dXħюF]CsZ:2_6 IoϠFݕ;PK!vnhtxl/workbook.xmlVmo:>ӼR~Ac&qf;jqBh)Wر?oxB2^E9A+3V=FEj!2\FJyϧ O+Ο drв$ii5%Vř)UeaX%fBq _ 'MI+ՁZ`˜ղG+)p%OMm^bS/-(2J^>V\Uno sl&]U2"kuVgm9A18 ɳ}f:{DA=V Z{\49_u5p]ťTK5˘Yz!:nXRcdMt #kjD2`ZqQ(**W xo9bOs 7n _a$+9*7QDh.G4}-JfYp |e|\8fLt,QG>^{Ε02g!0%ە%$Va PK! xl/_rels/workbook.xml.rels (N0EH=q T-CGQ JXε|zi:[g%ϒ3ڕ%=]sQRu΂= gTOشX?0q,T. JU "Oӕ=x1dR-9:vUjxt̀GZH\@*%Q8C~Nĉ ^%aYcZ*&1݉,]xZ>g0$nϙ6*@m>N43y 0C^nPK!{-ؼ' /xl/worksheets/sheet1.xml]o0'?D'DZ1ZIk853i}ljMؘ&\@|ǰ=֕DW&EÚeMї 9MN+ް޼}8p.E;)۹uَմsyRpQS Zho+~մl@k(ʌl_F**!nWxspE|u B!,/#:&њ};p mʪ/}ȩmTБ#i< 饧CىtYɫ08{.Fy]"#,x%,:T|_)yAIt/'2MUx?yG:_f=#X%/8D͢ג3QI]|`aS mMdMG#+UUV!rh&g;RRd?+? )~oa>׶ 86.!L_Σީ綩ͺ?+J ΡW2v((N]C &Pr!La\*}پ}wi@X5eNa4g@=Vø?xj n%ik,(g@uDqEK0֠04'%EdG$S@{%e2LS{Ece](`e2l1(A5IM/eVĜ6B\'6$F[c}hZeR=2؇c;vsOl1B|u n8_%K [>Y>iS/,bb޵ QZ篏/z~;Ys3tr?<>ox>=~׿ywSi]ڸgχjtlN&IDRd" ٤fR_ y^|&39#)J {S Mlii*Zr!ʃ(30,%DY!XJƱMR1] qڈS M`+Z;!|:籺]4? : 2H]Hl.sX$&2Ce$`i,Nyi"ZB O8E\<Ùv7,%>:B/'! &BIקYqwAa!h-Db+awB!y\a3azѱIQF$&K_t1$ P C$dXڝb7Ig7On&YJ`) !Qn \$ @ .lȂh !؉kT/ί y Ǖˁ wc?x ݧňgO"%H iBF. )w!]H ҅r!]H iB:Ҵe$pnݶw7 [T6ܻN_I8:T3,0Ob虣Ƿ)`I9`FK8bVqnoÛ۞&?nC`[+`('K2r9@R>hR%9XC`H> ɇ&Cm y2F#jZޓ12Z[`š5=-+Tq%izġM8:CgDoBi? ~9qoFPyߒ8_Mj7mM~ޚ9 9t>CΪUdttH:{.l@m[b)e)Y 9* nUp(<8́s4' ZG Cv\|Ox}Wg X3ufܐØ7d_`)ꔬJP8#|%ĭO]1zNks=U8*hF<Q%q質7[[wG'}ġ@3f) |foNɪT`NĭX `=Cq(8vAe78%XQKhډUYֽ";=z;`=:aWYo{ׁ*r:V[$UR^3Hn #d&đLC֋'\ox9%ɱ>Bk⳨_լ_[6ߺek5)HLOM,J+VIM+U23WR(LπK J I%%0^FjbJjg_䤦'&W%g+Ye*y*󋲋3RSKPK!$L?xl/worksheets/sheet2.xml]o0'? 0HЪM[m.v*`f;Mi}tTJ`?~9*W&u#ՙyNѷ)ruNKQBW?,vB cBRѺ{6 I!dE5|kO5.J/ث(QK! Q؛Q:9+Ob% S PW742Q>;ݷ@I$L"SÃ9U0kŔ~08*(n~Gmy,˃q>b7 ?RϖytI0"`S7H<w{ql6[+ƎBh)c!0RF ʬbSl-AIF烏3w@kqLxSpo8jLp*8ak[34NFy5ɽc9۸ 8s #;,F.g0>yDÎgƕm_š㛀CvĈ" "g]뀉h>Hd Γ?/?oן^Oo__?W˟w˯ߊˏe??_~/_#~=B׽lcL:5uALPn=8BY 1H55~q&dA";~c N3}yNh%%% Bŧ \g}cbD9u/m֐eLl6?ŘqA1+JL8'~.a.@x jzH:<:7\W6n m } wc.}s.8u Rwqݷ65ˣekOfC`r]9Z2:083qM&Wo7o&kƠV@_q7y(Q\s<4>etkQoB55)<\vh>uxLe=ieZuN5C/,pp\M8hrߠR›U%w]=_٬G19/&ɳ[>c*Ci9ժ}%[* T8Vir}Fΰ'Wqkb{TnKo,tp&)1B=)׬s*pvlo-{'?իq7y(>alrؓNY=MzCxtLpe9^G=.PSugSE莭^s4=y޼r->~ijtKl&CL .u[G{E5 ?)ۺ G_ƽt]hC sǖ߽C!/MMw4VYS9}ԊQ]LYWQ|IFqTuTq.*:Ue=F]a$\x-kÑlsoQ7|c> lTb%:;QE'+Z-SYoᙜG?tpt4](隻BϮbƤJ]8&sBi{Zwׇ2L4a.:N<DzAxʼ>2ߛ.4ۡ4vĒUg .޾}u7{Nh~woϕ~(9]W&O׻8Q2VeFd+-d3mx#(N^u:.B:9َX_}ݛN7$s=58uW&O׻8QrVee7miz+?zM;h✍о̍ț=_U'o^߼v-y>TS;QrVee=c=[Gwy.zMh\NgCq>t r_?+CT^u'j+p2;3Ҽ҆7nAMoX 5S|1Y4qqy3}f\8~w}/|ϗmTUkڮM%L hzU o$rp;ti? .8z/4>G^Wl_O;Q*u&lMnhzÛ`4] 8k^?1#" ]㷛=_h&\}Ƚv-kww}`fïND~ڇC%i_n&(s=<.8/l87z8}2]D!EFW+nO{Z__ljqz1aA]/97^hz2]Dsmu+n|&N8{ȼO7n$/N͇.UVyeV@duQhdBF=Gr? w:LDGyZH{ũ|n(̉hCK^MN>~fC/GӅmrT/NC]pф15=S7v-HwN>|\>_6N-z. ?ig43OEQ|guJ\Tx>͖fFq~olTO l6i6O(fFŷψW;\Taد]+ˮd@tv wԐ5_t ?-. _]hL9+B埯<2^ԟ0zL݂y\w^$%\y҄'w4})SU4q>...nz`Bz8q]h7䧧Y /wf>ℝxw<} 6zujDsqAM=tzqlNxή. ^fvl҇ZVz , dIa,m}y}ttYkG;q|]-]L׽Y+ L #q߲#;f&/Mzjkê Me}Ua3}4]: {E'f]MM\juer[q=aapdt L#}xa㇧_ڏ{~%[Fvv]/9)7],q5ҏ%N4nS7t`唬n'\ݽsH(C§]2t>dޙ2Rj,`AM,wD̓P_𴠟w1=ʳ=ayZ 'xa{]iM͇- ޣ?vCO5U&-%ĽhZ^8*;h>^8zw=oɫW䇗xnw& 5}t70zwz1cuK{E<]i?CSm_.%颉~5颉+}QT/&.s;>ix%W\7=%Ӵ o,TDrAMEϾaGEgW5B.'.s{>nj]ߛW~w}`cqOx]rqU~xc鮸&}4]x~N:;u.ɦqG_eZ7kصqH?ʞ:DQԙB J?ʍzxnSO<=\?WyMjavqJ7Ó8#u\׏#d'u@}qL6_F L^fB]/i웁oomAWNU _\{ ;5=]*55f /}t W.p\v4]<1!#ۺ=qtm`It+>moml,g]dzx/Mبz U/IwjnA]/iMMtftĔlΦ&}1]4qڛ=ar{ZYK6jz%iծ 0gՆc݂>^Lo;a_փ+GEnSugSE'=MMtfzO&G^+V<҄Mw0iZBk\j-zhh⤧KG/iA?/bxz'M&skbzt׍wVmMܚGFMx} 柲;*7_LMt V=ara\pʇ=҄홞Oh =<54ac†һOEQ>gC kWٲGgh^lT9mX2͖Wܷ(٨lTآm6*dũ_嶶en.YuUXhz-U/YUoYuʪoYǂ|Uӭ2Vߣ?6~OVe1T}Aߢ3]3[9^5޲U߲}ŏtGgڥFĶYuUǬϪSFGӅ'%^-NY-ZXПJwtV٥]qdͪ:f}V=*t0">.t-U/YUoYuʪoYǂ|URoc[S>4}tx=Tr-l1XEzv3{1+r _.vwV9]r5U,1eA]/9Ϟ`XR5MǨ3],1*|-M^9.ʵ%>Kv^}%Ҫ.Eؚ*񲠏g~0U,g8.?-ljUֿ.z^fzO&zZK|%yҪ.xoT~at rؙz5)MI.$湸^O+ǼN\Ձl[G|thU*oߚ;п $fuK?^a&ۺbi%游^O>U{MhXDmbb뚱adWrʵaEs4aħ]3,b:D%: zϞ`XmXmbb׀pvTn27\˔s4aħ]@YZxoRǛĭbq zϞ`XmXTnU[bXzt=+7y嘷kw=|w&tK8KMxͪ:f}V=*@tQlzɪ׬z3goYUYJunV9ܛԣ|k$ v];<`tܛ%^TuGp;^%l~3]$U.&5SslMmDZv%, ^xu-G ;e[w6U=L/ê5~S{ʵL0]$O7>ٯrߣCǦ&;ׅhrݛÙzXUwtઽSuz1U,qy'9fz&\wW}%οyZ_tG|?d-"ͻ[G+^ѽ邉iAeA)߼꛸[&15O3}&'Π,J&OpDO|&' Q.~x'}1k2/ztgM<2.9W˻<=1&7)ɢï.0][aп˻<ɂS7aJ & &Zï.ZRoԬK(q;sfa#0[e9oQWIX>@>a4a^vaV0K.8aV@8a>*4` "̫YY@H6zf~) ʏ/ sܹƹ JT|T'1hm 1++IJbflzHbK(q=oufa#ElIeX + 0}(`3/OVw@@C[|Y Y񕭂2KG|Yzo]f , x 0bk]Y@I=6[eݗ$WeuWV_n4_NV|e "/8_4` ~On6^vI^iNWVK]6>W.,_4t(ӞlO!fv$g", .2Ƿᜐ𲀂+{E驙p te 2WC>boءLte ^`}x?}e׉$hcȡb+.R]v VB@ =buMeg[Y]lq1z.3{~eb+{J[^2 7oz\e'̚_p.H}_V/R : ',@nk, `~̪q}37`&, ),`Xa{K4]qR/0{F!o./s'(acB^P 7x W-=@~@@MGSTBܖ7n)@ B4:)f˶R9X%*1b^Uh13tFgB-a( 1}XŸBN\b!fgԋ7!y 1*IJb k?.,]sOUb b^UY',g!O8{&ށGzb.Zbb! ޜbN 1OhNe[),[+5b^'x B bV*v\,,;l&<NbIG id&+bX_bVBy)!A<܁ XB,յ!{0W5 S | 1b{;XZ-Elɔ]VTf#s>DQ/bPэbL4W/f p!fGsB,J!B,,6Ջ*1 b^8ܷ*v\L&̍ 1  1 $#)1)̍oy|n37oB4…Pp4'IJbfkFxPZ0[/f5 ҀR!U}bǡ37>&, l?*,`@L +/+U!=3p1:1 hh)U",[oe _Y]|eu ĬSW$^W $#;/3sBC=XkE=X>@xY@ e}߆O_F/\ ={VAeuհ(K ]˞AYy`2gV}bΨ2;s\=0 8#̕TF4`PmTe[)4ג\jVA% |av旉0bTSf|ؑMY@TYY&qz]{Lo[vλr!`D@ʼnW׷S4pD FOPg5( |}HuY0E`bû8;zԉ_JVW }js-Maah!@y`{> l+E3]Q[?LN)0bx*vf0=_an'6?Se=gub![?Y h7l}Be[)`뇙|W0sa^&58asDY@ؓYY&A#tubD asG\w@@C/IIw2*²asN]}B]}VAǨ*#bQ!>-;!faf!*WSLszB, 紏sWIBb:l#D!}Ee aoUai'*rr zq3K>&, l,,`L09k:19''ܳ@DTaf\s),@@ X"t->( Ojub|'bi*ƱZB,M2y̮A̼bN19b!fg}Y=?:/,᫘PbV 1ӵ, f{:J֋Y@wb! | 1bq%̒{ 1 = 1 $'@L.(R]Cpx!@Y g}}\9 ńX" >~?!f%_it[]z1 R,IJubѾi 1n 1 @ 17 &v bf bf|S/bf-XŒ/cB,!fz6U!fL!zebig6ѾT]ћBe?1s95$̘pV!f-B*, l4Ộ ²+3W wQ^fʇ]ބl9ǫYg뿇ehszHɅ{Pv14 g|,pu]cȪ8p8a/'6[g7nhݖڛ[ a0aaJa7BY#ka~>#tO_P% W!lzQs[k_ 72M}N] 3K4| ;" [e(\ی.Mq:$Ss@L^lIu!wP11Bz1 hI 1 (El+Z z1B, 8Tb^87sPObqM25d̮A̬YJre" @w,e2_BLn"@K8 bm6B{ay:؏ !_Ps!,۰2,wqJ뱰QK?ۨytgg%6w_+'E j}epq73 8~IP-6, cvw#b%)X2+ħ AL+eC[@L.Q=~/v@,=}OH@,=}@b<5dbɯH>X^t$1bQdw baʏS?W<XX Њeh?r &[_/"=|RB옭]lӊ'tA?>HxbQdw bz{8b컷}.eh?s ־! A9o1oM[vX89bwzo?1 %LX{  ޾Ko߳b o*~/A,EKs2KsXb5<|TbY޾ӻ ,}A#޾kW!,ͣAK_kK|ѡ*k+_컝t5:={X0_ ,~M(`q`MX}Fst2q(xMc맳&A@s0#be/rCt8)XÀX:n`K_'Ct,lۅF,}O7A,Xt=X: XaXai0_×|FXV )^t_}$X w bm^rCXY; ݓM ԑ~/n,\rT_Q6,/#|]\?v))^}Wxeܻ+Rzɰ7$ *=:Ώ s(XOrXN.l֯_졷|rjKާ`^`QoS^ϵlg8'Ą5LKԃ;noʿx43rկ'5aXLH} ;˛S<'ʥ +~ UawYq )gw4=bp[ai{a2w aXLW!^?o/6XfxX/AVԗb]X9& bKS+ ' U,3~xPV,Ma XLW!^@,,^2KSߗ4 b!XX&@K8³Y+슒`ݭeˮ_HLnu+ALW!6W`}}u!tVhQ^i: uvhۛlYqA;,hW]:*Bv{Fmpj'Ai<+v f `rr˂WtzX_ف +-NA4B< vwU,s،_WWG7,۰,nFK6MUZ,^76F`ば& CX@(rLq ^}7eKfn ,}^P!,}^q!,}aQY8jbLy-0e3]Ė,~<&W.A,MR4zI ם(ejn@,^OA, V` 1 #B0jW{ @,SiFrG#ŏB0H.A,ORbe7] @l,aw? bYŲZKf&;$b۱o^ޜ"N+V:=S 3ߧʹRq &)R0.?+$F,beA4g/|KHeJʍ,\v|wh 1[q/ۿ|_QGɸs^JS7{Na|^%Z5Z[R*-󲴖ŏ0JlXe9KBi=C%0f]؍%SX>0Y A `=b d %^c@,^ˤ` &s"@#^.=%kX#Njk]3%i܃WZ^d]i {8D'ְt`+^qa4=~G,g/?C(݉BX>n9BPqb!؆I1v3_]-LI'aLp.)tYB 4}#9c!Xe9pB#g+0;Q'ߑX/wX| AEoï",̾ϊsd'Q-e5g/Kn徻eI,\v|ٟB VY~z Jo%`|A,sb pw^Ģ>]#a, Ud`a|0,XfI~/ X&3LB @A_I8zXfoGQ}LSXe9BPQ@;VRVbBjvs0O3~1B06@;BPsc!sQݼW1~n05}[0 `,`,[a,}?ڱL-Xsa,mm:Jjcerv0U_FXxeg1'`<$@W1r25S>Kߞ3 k~< Ҏy ﳫa,}*aL~Y@J&"}efI!/Xa,}:a,c7sU[~ ~X6a,ר`,[`,L6`K8Br!/rF 4ce(_f*!/&wob!(_;l$İi`. _ř;Rlx0m7$!7 m uI Ov !7:=p&OF!7r*UL"ga|u&Q(O 膠|F tC Û;XpeJXV0KG0D" c!lXe9wBySQc<(l IԙDFM_)(_RucIaa,k6amdWVm +7Uj k4`mg?p[2Ri+֣xs_snޜ"R(ީ٫SM >%jdTr^|,a)%|RP-/&g<˟./= .b=M@ .@,ث^-t4!*ŝks=SU4 ;a 5x1@A_AsrXW` vWK@,\|ӊ-}d /[Xڹ{AKK7#IK77b!8{*b`=XVLQbYYK7p{{Bf 6֎X^tsd:ts=83E z-+OJwQ&`}tQŋ0^" mؿ-f)%Ŭq*`Z Lǯ1we>| ߑ<PWGM}Ov ֯zb]w;Mwe>LVwW6Xzn~-X@(#!t؎l1J8&Ah.ޜ!2+l.Ʋ<7Ac2k{Qŋm$-㛴1w`>xηNW;Z@{jXGj"fUST&O*U Sh'[J0yS ` Dc hֽZo*`rI?Tŋ"?=_n~1vs^SXaLn3$Xt`,JcN m w93Gju@"Ha,J|0uݤ:^ |^ /KS$)j/]zX͉tI0&eu&)^M[&رaE^|Xwd_Nu|tZ U+[2]XhR>ߕqwJP_^iH=`ᵻw~HdQ6AY-ޑ(>6?N?-6N;9ۧ`|а{~vbP:f{{\/)B/IP쁿u4 /SdUNj_ykorϐ&0=CM 0OT:pdO5)@Z #k]+|ogB0|GV]{9=ɜ Utz{rP}Y}wQx=|2[iJ Sl;)PZp,';pWYOA9[[^4.T?OPv-|w3c)YdwCJRL> @`ld!\Ywa}8 & ,EYY:^dYOQ| b=>t<Һ{̜ǽ`{K?254PNA%hq%iMve9ӈ3oT/nA; (;i?CPF 1v:0f kjσ3C? zð7P ( spPڟKyQʺv@TdQ}tI0m0W>Jo$8qh~qeƢٿ ڱ (s0J= ,mG @hȢ GQfYNj; &y,!`!Yԯl':8} tŘKU ;(X%,SxhȢSl ? Y B0YbyÙpqS bQ/aǺ" Ƕse84c>8- k@+nX:pEًPfm/LoYM{ tmW[|= d棡 ۖøSPWZNmw0CZp4B&˳-< ,1iBP>#b(l 0WcxwGK/P?mK7Y0;m x<L|a, P,lr(9T=0)a, ({*aL[ 8O·_x_?GUN%mާʾIJ:J8y) 4,x+%Q=OPGö[q,nk0 }<m{[ V,Y Յ^G2e +@VIS2jgfb2 ,ETf/N(`BSgAvs~_Yd U 4Tev;ž&E d>1/7Vdjhrk_/)%@;J?-Yn ]Y>2:^ܘYn BJlWA֯Wg Ғ + enf>Hz!,m~s]]e dip]pѻ@>+ d[d(VU,}~~,}~@Y *q@aX4@XVBX^䆰 ᫍ,_t`06ZQ&{+zTu?ђ h||ާ[yH%xBPKww}Sqwxew*m]ީ7!ȰtX5:\7Lw+rN/Q 4Z.Qr\^G67=ABq˿jy)%Os=t|_VͼoAM޷2o.{y^dv~>(MV +ͽ=`aM_ՆFT)ko{L});>S>=X5$C`xx{m@ơ-x<{YܫȊ n6Ym"wX V?SN٣!K %c]JN .P\7c&Se7O`L:`,Pn bfnJ8G%ڎ)G v,3u:~| hzXF9kg5(l,fAÛa4׌LP B+,DaʀX{185^ad3$o1!Xf{ 5"{#00mDZkok{2EFҀ6]m8uYzRCo*W6\}@W\aX k8Π6]m³:= CW1]ekBW V8tq+]Q@9LBx]/!`XkxN6]m8Htq ,35to]mCW6\WzXƒ=YjƁk[4]c9'Q(Mҙ/xj^Q{ g} _-W0$Ȋ/l#|kYlW+ )X:AXpv+yI)X=lNޙ a]B-A!`XΙOA9`| uXq ``,-|?* @aaltoI<+;oEZ1 ƷcA5ZurJ],;i$(,PUPKB%H/]Mȇw ̈́ru8&[.X(V{?%8y읋u/%TK^fU}MwC?C~r/ PJ~K0nbx+Kx5N%y3q~]]oKg+'K`Vt/c&>?#ɔ++@X0 }?# L\M+T@\W5b88qk?vN8jPơCU6Um8TqoUN `U6VvI솊Z?xWઍU6Wm8\]V~VV{jd=*@d3.*|̵-8dqj!CV1Ϋ)'d@V0T"j!$k;fooIݓ6#3>w~zRO_2[eqTTNg&E ?2ο4{)$۠9dJcN^3G,E=@X_֣Cމ!K1u 6^CK=w b}a,L}a,SeoƲz`,y'4ER ta Ǝh_a5[b$3cAؕ,{ D9ӂrl@lb6S)w@$pJ_w~IQtnn, ,'.>Qp> QkY`uDQV*e!8m[)('BY0)(GȢS}, (GYa,!Da3܇XjlXٌ_ӿqNk@akQ[؊nUj'Z8jG 8GʑbÀqcVQ ^ hG֮5GTPrۨ$(+I8 hKR!7 ܳ(Ops7 8y!Xr_A㶰b#cP'@QcȻ`,J8H5k8A٥+)()Xrcma,9Ƣr3`c vb H;L=kdZ]V0PalXM ƢO灱.alØzGp׮oa0ɛ!`,XN::n7(Mt膬dz̢{G o||"yg${_e : OKЂ59 4&^Pu]B9X/Q?'\q/8~ @s(l}-9)X=X"qz>RpMY8POzÒ%`o*a 'QK#i?UC2:`@ (gFv?HzMC%AC%5ۍ ++Yʮz dȲ#a,yV>%@KE *bwņqkW6]m8pqj5@CV6W KHN=j`U6Vma\ઍU6Vm8Xqj`(.90/=!6Vm8Xqj`*e\PՖUm8TqjPơCգ:^î dj V+,גd Kuq^-V[GJWjqMG{ Pף+$^1EOlqݛ9Xei2be e(ێ%,g%8y'aqRg՗*A9= ,P7a3a_O[c=}Lm†R/F3Wx/X0&byo17aMg47e%#(k  `!X=G 97f&&qC+fWI`+gZ^mҀ A9" B04`cٲ9WP y{l|) ^a!8{=Ev/9XS@jtj`K)//Ҕ A9nB0c9&ymX(/ `{6 `rKzX J7]`: X&^(`b~=Xi8-TIA|"(*EvB (Kjʁ,l/A7I di;. 2ؼ Fd!X*YF@a,[@i8qKBXVw1^8X6]mir/pW1[V[h=, #dv8͗-* p`u\!89ڻ1]W2C䌥$x S?`Yǒ5uܼ{  K6ĢT1+{0q]q  c!0&'\*=? BprR Ȓa]ЈqY&_Y> AH+7y3 c_Iӌd0ƉGS~MiǜLNWv3]~8ӿ5㧲&Myߟ\ᇪIL͘\\w#C>`Fa2&Lai!,;w'aiE;ڻ_Rpa Ǿ,KXZp u@,-}r &K@Aa;dcw0m _/&3= 7/Xc`,q' XԁtU$m,}}G,}Uo+P. c>[Ս5 Y0,f@6ZCI]˾n'Ҁk4wX ׬U}.v VwoN>Ku;Nׯlx1~s `;5Qq>l'M l}clK|.oz ,uw cڕt|M1Lf`eܳYUqskb4| [ofKJ~4c@, gK÷,* ?b2|{Xί1>E|' }ڋeci:Qx6:nN2^U`2ݑ>z4.tZ[g|1<" |pAhDyOT?.4" 8-w lT`@%;1pȚe'+,p d ^9Ʋ;Ҋ;Ҋ;ҊZY;F `@o*F*zm n:_:pFˇX3FBpv;#QI ޚ>$'y`t6]2 Wrv4ݝю`1C!iD_ut.긹tN1 Gc!VNNǬ:!N6 bIp{v)PÌ.q:\/3Β`1MɹF]җMraiBp>y+p^~K0,ƎX$a: |x$^mƿ<}?>CV[>`=c)\:p᪍UڄNjp᪍UEj j֣:f"&`q:Ų<Њp@+$ɛ#Kte:EEd ņ*|eS+XYeGf~T  1CE>C|}$=7gc⼘LZ^!8؎*1$X=VIlw 2Ih:es"3`;X ::nN4-%K3w d!dGFdzBdcuHL@K%"@Ճ w cm¯, b!bY:|Rc%1ƁXpRX8gh bQ)E +M %qK_lIv丶 $&L<<Gfd&]f^^Yx<'vȅ;"xb!Tbل:AL?oXVb!bسG CٳA[]<X@o\&\k?j1A bK;KkwlIJ uʟξ ξ ξt;/Q/=3s䷕ǍW7>(YBP|PyPAxP~:fBXVm9X`ՖU[Vm9X`5/% Vm`6LpVWm9\pՖU[Wm9\+W \?U{̳;6 U{̦97ጷ /,x z(XA{ZMtw4%Cޅi<.Ȧ>ݫ@^96Ȧ)Xem8A6M Ǧ`߼i yii\o4.x%$z:cg>?0_?*%g[U=/_?gj?T˯\AQ>Kؙg2Lƛ\mNe {Nddai@5)s2 u<޲>N>os2/9OQ9V>}VV?{V6ђo6 $`,߼~yHFsUȻhf3!k`Mc%X<0~J;RY"TͯzP@xqJ`\xNջT ߂1E5WXQ@,j`I]Z25+_`6A 6^ļ߻Kp%bQAyqx|Qb%U#-aX!vH(Q}^df9XԈ `dj`6B06cQ>$ ʗ)8;bQ}F+X.N ԰b1Lm""w:G^)nXp20) WO:*gQ25R0׌lƔQCc%sL mҍ/Qv@6^ caeFi%6bfXN(r"_K XW=]K ̞mӏw c ~"g6&@7c8wƲ%?xqf0``scrnmc7EU(>Ce/X$(\}:9Jﲑ#w c06 D\מ) cƒm8u"ˢvXVc!7c7;EEE9t=g=X{HP^XȬ,`@bm@-r6-Љ`r&QίP44{{-x CIta 'wW/0L` _]W$@,n#]AX!,6!,X6^I)%uB#Z ~7s%i  o=cQTla,4ʱL>"a,2IÃw c09V0bX&gmXN'mXXa jB oO`l,+0^n,' b|KN&XNV!k+)AX&lBX^NglBX!w9P' 0cG̘`.aޏγ ,Q+̻#럳^DͤHP6~4X3l#b߬a1w S~XfLj8c6E`< .a,CȧX7j1I0s6a,XƋ۾g0VSOS5{mcw9XV2VvM—^A%&`l~)Mw c}a,"=XNlcg/1%J1iRybXVr=+Lf`,s0ƶwK3&ڻ0nBpxc;c26^1%L1wƢq]1v3*!clF䝫KcGXB0k6XXa,}Gam7?XR2!P10&2 `,sLcQ8 `60%mc{<LXa,6^pKb`̏ca:xcQ^cTsae020 .al,_caXZa,-~;0m,Ƌ0d6XO~gO%0jd*7֗X&wi;R ai{"aiohai;a&@XZ ,-~H)^$|Y/ҀZ^,-~w ޶1+xI/g(f fG a,gJ,=~@@|m8w?dSM KHdl wpf ud) wلЏtlf`,|R 38`,}~e`, .a,}~"0>CEc{@"?eˬ?xqf oa,a,ޥbϿAƢ|B`s 0{跺"KSKX{0ַw+XT0;bYAg/, `u?} BͿ\;߇ʨ`(bi/p7d@,m~b]XFKߑ"KSF ?e˔?xqn oa,m~ ưjc[n,7CP `,kυB09 F۞4e1w co{0wG`,MXM`m8/ N0X-V\v cQ>~{?#!})`0.>[,8?dڻ`,m~G`,m~Oa,0mShŹ%0&}IpB0n,Ǝ[u#,яEEc2˖)@co0Voڻ4=XH fa,XƋsK`,~Ccik1 wnT 60םci{l TB0k"U+3ҏ`vƲum8¥/ N0y0qo1v\v$cee_!0^w&.Ϳy&b! Mr`v u?/'X8% {{$9u(礵HVy_VVRlqpMSCjYH0;UM=—f߳,~@jE1r_垢>=@X{k,3*ՖW[^c9/a `v+|r*DUR+@׽c%}X_[߃V[Zm9hgyZi; [`-rzrjߋcf% ~w&V[Xm9`OzYi;} etZZVՀKF~y߽Iuǿ _\^v| q8_hXu̎7X{6>  K# m,(_.NP1哐aӁݽK1{c I² `L>~9?}=m x3w cG!`j_*Rzc;yƲem8? 7tc{cwË=XbƲ {r~,QyO` w c/Kpwc!XNh¢'X%IBɷ1Pb옏 ^yŊRٸ1eL0C?>0%Mz`JB0;yƲ%V匕Ja,}|CcS{B +H7 tcQ9 6(X&]bn`v,ڻ' &|ٚBzMb!<b:6^ba—9 BnccرG 90yX8Ћ)eM5LFtNhC9-FY{J AgB0;{sh J@6 N)>l?f?of=W,}G7 oU.0V>c)<Q lgƲY`QyqS Dg V)_$}_*\.go)>Ib옡 a6KcG_RwR0;pKC߹=%Xp.a,lO;aϷbmeʟ-8wη+ؽcf5,\GK^}-+}Xw^|XjrsX$|f6 ~Ƌ;b++ވĢ|`13{`.vܲ _>Xe0;KXh )zIdiۘ@?EM\d di[@7YZ@`Av׿^nDd׷)VYQddKH@ξ /dSœ _~Bz+ {N푞l4v@Ư .B>{' /Fl@šA krFKMal0Z+6&`,6 %zթ0v1c6utvG0{K=0WK߻XNt ֖CX[`ەb`y:YRڢ+u&.ltV^ߊ(+j[]דs t&m9W[ٖÖ똳ޭ-DD0+Wq wn}WBW蒱W[tЕj+´/ V^w[(ݼ_JSߛoW{JOWz, W˙}idpv+T wumpK%`/b!7!OW?b y QZ w; _'X8aB`, |[X}`KKڱ0Kq(K}f K @=.B0D þd!`l+ccH TdՑ /di{ dS bn!Xd!X=U# >쑞,d,_!ؼ × d!(`,۠'k.g+l#YWd c!XXf"a,ۨhn7/Y0Se >xb옥?l/Ky KK!a!XZ`L~ w cG $1Y%4C>/vo//ec${ cm9ce &۱6 x7 U>XgSbm:,a!X ]&xS:n_, B;#{" ar*8@o wzo&@X[a_ L~~M`+]a[a/_a,rrjk,W*ՖCW[\aկ+< _ `-{"똓?'dQ \@V:NXtmh@W:y`qڻtn_; `+|gWVPi{- _a/XV2v)$b1#I~3=1xž5]oU pf*΋1Yj~)~O܃9ed F? ?$؇p;f/a|9 `lJfX &{06c%V1wd!XmdJNE V,j T ˟# @VQ ?U>,1g ;ٯ}v7D? ,vp&)@ξ{K8d}#,}gY:yғk lڸ{5 d[@Oξw| Ü=0_NY^d!dL)YY:Nt~.a,}XW`,WL0C>eK(۸{= ci[ci{'KcpN@cofz[l;,jW(`,~ 0%i寎OXV`,4e˜?۸{ci[ci{'Scv }30 `L~IrM)40Ͼqu| ٷqd!=Yumܽt-t`" HG? s)7 糖2GS ~s2`d]XҲDCDRzculcGw/<`,} `,}ocow)ƎyKNsp7cBL77BZ.a,š|毎Pя`uB0{ćlcew;`, c!X`1U{ gh2X60h-`rGcCGß;w cc9XmXZ`,M~0mhu;䃆ba,Ҋ1#! }^c!X&wt06 `|(w cp;~G?6Ց jB0{lcGw;`,|8c!(XMNnj'ce\9f,kѽ`XQ#utl0˧Џ %Xm5~`m,sIb~X)ؼl8RO/0/\#5b%&wt0V;,v@aLVQő K j'dSIuw=i^ cw*1v?os rd .~9`,cou9` crX:a,~`,~0NG|˟L0N?|ScF)e06[ЏkbQ4_F: ;;ow$緍a{1}Sw: ,}~ XG+Ž0@Xxoa+@XGCXCX&@XS 1]ham!,X=KzW`L*O{`My gٞE60 ,'Q h`8%8B'a u``bƲ6^rX8% BP耱%Ǝyp0/z^ w4w c]Xzva,0sV u-e2ڸ{cЗ'k} B H7Ʋ ngBp>1 {) `4_,>AK)ˇn!"XZ~N {6eq /M ͓1ƲK4b`,-~R0c00'w ca/6 :c!X`,{6eq _,`,6~,eci]ۙ^c@l,_'Al0+ [ 6rkك=J )ʗ+Kk'^cxur>SW:dCjy'[]ar] FRM{:BC"lPq U ~<ɟ1gv\ (|j_m9x|xP]m-mWz·,.S\\Z Y[\a_Gpj\Q&@W:CW:vKX Ukoз_`i{ֶ*Ϋcv9<^|+zo%_Bp`X6K ?+|:o`Ʋ]]q7@~ ͖:fb [.q0֖X[aC k+p7,tk`}rjˡ+˶*a-@hA+cGI@+lSwj+0OgV[lM_ ,6alMja-1cKztW@ta+|]H-rjak,w\a+sj[\l;VWgW{jˁ-SI؊ @laa+,cF`-r3? c:\!XlW 0+p~u*pWrjˁk,. V\Qu.#pWp\v^^`=ULNQ@US2.+wyu|`u`a|6@,]{ &׾)0c7~Ӧ=3_?P:_]2t>fmIy/zWҴw`rk+,+iB |M˗ WtaÏ taZt7S_ۿ_ `GݿW?*Tl9_Lyo~gegg|x6{\y{/y &>˳JY>+!ܓ#&`6?gA~G?XVUiJaK]5fTM6t7J/~֣Pyr^|xa^ݨ>űFԶ QG}c3ˎ)~}ojI1/]6@ݧ 6%ur"ŚBX.KBp`rf9VE% bᅜX/1)%*dTIdc @i/~f;@?E)2*!X 6dJ]CalJn1@V#!x;a iLMl6 d!X~ݭ;\.eF7,~HdQI5@1 ˤA,q @BpH:B:/B0hi(Q`y q6ZAJ,j8 &lГY;@6 L5Y`l,#+`m cQ 7&O706VcBx\O_dj,+1+I~}N4@o !8LTn=@ۤ-3Pkds]zKV̎v嫑lb9Q!wv](on6?l$(+ jfk ! p ,,נt=AO,~b nn7/E?q@vAFjV Fҋs'@7٩t#y!#!HzqQzqN(8FB:iD??E:(XNMF o>Ar gV_8qJuce5p{!xn8ٙz80e;mb8{!8yXNN(Y@r$-pRЁ[6MAe n6͋zP4zoe&9y˱: GW$9E)6M .j8խβ٩D99'j`^-?d ,nʇQQr)k`uS pK R fy|K\ɦQ %8lNh0{ dS%t^3~B$~&ٽͦydʜ遮/uim|I ?+ො+1f+-unkA,D9=@O5(%bAI5@O /VfX cwW.ޚ+'*@ s;L{ [_]LȒiqfJtlcqIe|H$1$tI]1+bY kXw3D-(Fu nDڍHPb O|,tt#4aa^эFBpX!)H ,v(Vg}#V>KN8ƧT-kGN#rIJR 6 1I>,?^BMAy6@5F'g `@>^LY&( ҕc dS"XSr]6,TOv&xîۺ1x/on@;!9a{g!R ۣ˷)Xv|jtC lm^U s5Zn4h ۣuͮl:~{:Pd׏o$8} +h$&I\bQ[NX@\0_ 3 Clm=QWS&~` c>0t_8뀡{$#@w$\)p p\7I\bn})XXpuL إ=h!)X Lfi5^lܙ3e0$1GlOëFR l=o raJ,C/;j8]lR0{aӔCK#aΞv~Ov \9k9h=wB(pOӧ]|{O$s k@rk˙3;Fg-Ӄrj-l˿=(!-{꼎C{C!$`jY 9 )G-wc tk+PV*|5!vG3`[R30a?tUz[ 4BJ/D/h Nt%fn(7AK}ZK ~KHl;"S9!X>Rl/ǐ]-auU{lqw[95z [Prf,( p%ܗf.[h!JB۠'p뱅bBP6Ղ -w ^,4~.5hP-(;XP Jړ c'@9!ab@\X8dXP0FehA>(m(YI^^'$z"P  HP 5 LC\CX@mPoX3z$58f\uիsĚ+Bpvv#5z!XLW]1tQ*FHB: EJ&\K$En@6 (l:tE5ޟbu;(,gK@9;eq# ~I7^^8{hd%u.RyA "jQ|&$!k0>JcJR6PJ1!mbn_v@RG-lC n3':lQT!X6^m8#XgkaVgj!V'Ej`U7)bKM1˱㕂W5ou5 _/J)He[X6D ,v\c ^!V/ڤ`OWM@{fB.a,P"n^SU4{Wxd2l=zFmp YdL6Z簹 +@?E׳e/>Yʡ@m(m ίx[~}l K׽YMVtij=CA8Y)do|ѻr}we娻[s2`|x7[ܗe2wZӤ}vܑy㽍TO8th4] 댓KT~8I 0J#r ~r rn@ۛ1>C &Gš1g~`GeЍ `:Tն/ I'2"! ez .z4E 'OA,Ma fMX{ 6A)1bc z fel>4 66b `]cb7)Ģ;pALn @,Bg@,BWXڅ^mqKЖ'X@,B/BXѫcJbi4ۧa}/LпQ?[q(_*2F破p b|D ޓ{Ovg#+'p) aT~>_j`;i<͞__c+ƹx]z|9+`r1>:enW;@XBpGU33_‡먏e-@̥La,Z؆Kc=Ƽb a,0ƜX&06 \ lAllUd!X!(`: ,Z=7)4 }Wdc@ V+Yxv? K@@+@6 % _ o̧E˜;=2 Ƣ9)e> ߹AI0-lRi])l#6ҿ90&NOҿ2ڻ6bmW;5K0;cBI#N:lQL-hl{}axy[zOX..傰CX&/C(`<RTa]BX6aaέ>1,; W0 Qaaga!X#? a!NA9OJ~oIEn^aYR?aYgW1,KaQH组([A,kX`,>ք?%&N #qsI֣ bfg d6 H O>vs,0VPYe 19t X:ޕA:w cX:~}`uQW f{0}, jd0VhaYacc7w_cNv+XA, ~ :]6am¼_ݷ`yGALX\O%KwW`9^}dCX:u ,i w8(_; ?W~iyQ c+X40ٍQ[`bm@CXg }u ``yqj`qr2kt ۻÖc/~>ymFF?VwL6 ާ<-?f .?eyd)E+0+K[lOX\hĊeg^aYR`;&t^cՏ81,<ܲ[=[8 շQc R)ߣ0&;[/)Rb1|U ]=kճB%`lFq옫^կƱ/l8 Bprci{.ci{ c!(G2w ccq#bkbllW.^]HbʗZ& 괚a,¸]-1R,2 &__C`r bBS.A,|y X~ XӁXW>^=񇱴 !dm]/U)`,B+qzWt XrX[ v8qgHB!tX^A(0ܯ.#gQPܿG aerv+V^i{;xW&{𕞾w_]4wrBXѮ-E6 l])X=1c]!Ǝ^m˜|2@c@,}}gA 6 RzS `K_o _VR@W>WXB:$°g,-{-njy%:/R3 ,¶/{B>X`60_a, )j},-X[g>\B ß=h^80^]a,#c!)C05caݗ)0:w b/NA,C|B0#>d?XhpK=&`'`R1E{ѽ:M6bm8X1k)X<W{jơW<{j阧"M"]zsyHVŹ=l8>VSPus ~N_2NQE9mHo]]Kxόz ĎY0] bᷗ@X^AAXۃ06$X 𕆾_jo8|a6᫽|E7HW+^\xsOa"Ҭ_Ex ywv `{ `K;XV/~`a bSu/_NHl1;& ^₱^c{Ƣ5[4B89e)X˜RQSK?ߎ1OQ IJW`L~~@l > a,O^c!LN`,|O3a,|0%X勓 |sX:~ Mc> y=hޔ!X=ᇱ e0N wKI*k06I bؗXAAqk~}Y,P*"IVzHW8;k/.p=W>1zxW6];SPWơ+|BWfg ЕW(a 3}.xݤ=2zXjƁK&yqY+|R` ]!X<f.𽎒}7)W6<3vqj㰵N:",pqjx=:o ]i;; 5\!xڧ\mNjž/;mv=BW^6]m86[cTJa+mj{V1Vָ@+/P'm@CV1>I"^{}vf W_݃Vu o}|pO3}ITU܅2e[$*'g^`Y?Ϳ?DA@ E2D$H8eI)!zzGxîTE 1_U"N<< Ӱu~P6pζ,V E"oԢJ@5D-QƠ ȥ+(b^8q2祮:^vhMwZOEPK!]Y xl/theme/theme1.xmlY͋7? swii-7dSҲkX<==ӛW$;\ʥp:fѰ=!Q:A/^yhG8ȧbXN,0%6)6e{c>Vetf}Olxa_ً_'Ͽz'Ͽ;w8:4#`'CjKŠۡz c xs Ŷ q`eA8f'p1ej.ãE:sO&BǮ{(SB\R'tGd{ ZJ- ; ~AEr8dޜ [Yvͫ(C8'I\Ù:`}QWZ[/ja)wV-FEkpةYWzRXRR#V;ayI^Ȩ$ ZxoPK!'8= xl/styles.xmlUn0?+Zb!eRT*w(J[Z,ΐ|oLvdó#* U2Kǻ[`"K•)>^JsvOA!W{*Hsj*aRZM))Ip? /!D' %jbؖqf=FX]dˁjHp#1H}GBFU p}UUO.O ) z{_45}`V>%AjIqD"s; O,)W6y?DPp:+"?8wdg@ $C(Ca C E jE&0'> dt ]r:ѕ%Vrl_j*cdd$ȸS)gPА{)6{V܃N;eQlysGXPo-§꘏-B0eC8arp 5Esً`QWF*~ϓ:Fp, s Ѵbo~ c^9kvg;)#%}D{WH^ß(HߣRvu?؛OXm[ h ew4i;N4~OK U7A"ŧ;HƠy_joW7Uy`f4zųf/(>&hpj8zHv {xb8*@{}̓8 <ٜ,<86* /B{[,xeQQDI~PK!cAxl/sharedStrings.xml}M$G]n δŊ"2#23*#.,,YMu7!@F @A')gYg Ufannfnn?_}^ɇO~wO>?xۯ^>}_^ϟo>@݇ן|7G˻o_'/{/_}}߼wwog>>{ÛO>?>ۇ҃}O?~i?.vH?z]ޥ</!_1.r[yȻiKR5/U\g۵ۼa|ctiSv;'d3l^#-j{= ?N\n>%NۺmMh7b,L'[ѷ/l:li>Ӣ"j/WAQɇmWmDidg5hOu&eeday¢؞[JC6Uf,B-f6~yx<+`GnEEue}H&I.]҆l(&/^ +.+gҥK e\3{*]4l_]7%tH'U)Vq5ohiL;Ŷi3Hb~dۊgq6h3e8YbjHN dˢPL^^hiƅM~ȷ4-N iQ ]+,v fu = NlXuѤmc=j% q$i뜏cXYu5tKeu8W$*jzSƹ :/Eby"p8G3 IWNU _rm<'&P8W!N!(g&~Es*9 =]BljJiVyY? YJ-jO 8 lXCA\?>|vUt ӔB!:Rc *IRivY= ;n!r]Bo!YK*^4Mw'j?*v8n% ^&SgՄj*^n/GNXۚO+#kNڸ* vAKC:^'yo[P.ڪJABЋkP/\ΕWJO6v)= , 0gp(Wt|X͕a5]gڦsږ!X͢):^4X_R Us[O K ِm6즕F r[^+m@M|;N;Iϳ"WʒVfO|bml) K(aӼbL%>X=p3c4KEϋfvNe>E+OvK 09fLzxr+(n4mHcŖ~qa 3Eߨ051;ˆ"DYcq-۰r6`-|^ d+3Nse(W{AX!NY΋MHlRTNOfbE# h+MVȽڰ= MٻQ lDZBp]3x\|/~AwW ;fN*ASXvM rӶl虩,j]jqCJ+$ٕjmbP~Uf5t0lߤdmˑ(:&C ^d5o5P4>ENS#V cj%\ "VŚWhY]AvKxp~跂wOLP/4Cq hd`\p:&i{w 5}KU')Dž)4UP ڳ#i$TFU8X\9Ci ^yR7;wA$E\$\5&Ӕ"&V?b,3"4Ƹ$r& sdnƺ$pRGLZ. aq{7$/L4|yRN)|+q Dzg|ݿ}sBĔ Ht7M_=LpfR&l9V!vt3B;-d*&Ih ׂ#da!)] =mEGF=s]~\7"]BQG{^n`)j {؇llj#}^1O|y\ mV5a`c]a5a[Aԛ *cKL?NIZhժXX y9yFa@{U2xעZ Zpx4o,mtWXe WA8ӝpp٢zV[ak2VwE܃ aErb 5 2{hUpd%=Wvޒž6=T+-X,.Ur^ `4ƊyEy>j VQC=-\3 7 VE!>nxP-R3cii|/D9 6KrTtӺ&~QX9LH}p;04UޡN8W.:dY𓈈TC:"T5lXhWvvd趑 u#*ଣ̃Y̷DvpxtIb')G. T o,>͚'!$de>=I&;D /E X޳3kea Z68ܬZDf>Vٌj>"\WY#yLuj͚'"MvGWՊRt]eh:sYՏvd,7.M4T$zPxZzVY9NPٞ.OΕlѺ'VfD'xo-$q( Z}Fn`x0`ڃB685Gs1lu/=!wj7a~ym04d}ӫis4V1*I;Vb}0 j Nb^osrf {Jۈ≮ƝkI]8\JdKbژGA}c^rM尓WII \ p2W:؊e\-]'S%҉K4瀼>&Bl|d1$rrMHt3(Ϣ%v:HW|"^ D,6 7s/G1DV%"ZǕ:*{9Wc+r\% MʩWc~)\z/D7cH%0# r%BŻ,C PQ*نW^ڋ`̀ю? xa?f A*} xղ:b08 b GX\No GA<3T).W$,? ?vtp guMu 7;g˯ԟbzltmʋDNŋ~cq^{1ڝs7@t!jq<}!"Os!}585jfBuFmuAC3;:=a>׸ff,e^kbȧ1𑱛67A:ߍ OY*?۪Bζg>ְW0i0vVZe3kاO 6fГ#AI|mEUy?kٿ%HV~G+R5a H[LQQaz/}u $"1ozM̥IbO }Uð`dvbDP7ў܄=?"})6S:Y=LZHJ5%j/ieI|w7]wxx@;ja(,$JaZ""pPhmZ% {W0VmҫVx~OHp[|KpPSûbwxq5yoM\jZJ%nv;!l#-׈_ܮ(@LW1i:[Ob=^Rs\R½S1TK(1 ?J >ÎH"ka6!1;g#>MDt( =J)Ao7y)K%M% Er%6x_MoՂ_}WThcMԜ.]).F,8u!&iA]mؔ$g8+Ϋ'~\Dd=o`.rq0F\y1Oī)2/&QoΑt-RDS]&[ E: j"²NEC{&vˏNHu&x 7], /Lxpzp6Qz];QlFDR*qD|A҂[7{X=VeGX;fCUOItw Zt+`N]I]S)fvĘp~,!A-3VΫ(yڼvhs0.W9Hv,3g@ PZdBI0E .(8 TUJ"ƭhXA[)8IX^ʪgMA;xQ"~-x'~6plϫR'R(N/P9LRΨ+Є50h 7yKCIK\LmJY]L?[cS# ,M5´ nGP*_Go!!K9N, wŔ EHQ5SB?eyAi{ĒAK4Hŀ'< 5ec5GDT,)x \IѦڤL]%M_϶زςj~~+vGhnU?l1V" _KY }w{$.W{'u):! A4N,+g(!I\I I!+LqC]N`e*ULx~K &" yXW)dN!Ì4"B]QmR&QIK*L0!bnQ9hBgUY-܈H9QO#db;ꝦtV fFlnxa3bǨ{&jC老_=C%eǦC~,D~:6* ,y:ñҰq>P@Pp9WD[Je2JbqY01\?%UL]'f;=;{F:kF $rHZE57W7"\ztV} j^"¾^T?9`B4^soxvGDdەQ^ =G;  s< ;ckz!m##BV/ !cs:O Z{b*Gߗ?F:]൘̭cTr5P9]{6B$EOF +aR$8{ nQQ!+Z0@OGITv2T'k4M cO8lf,{a/6!J;;%0ppGlPPiaFPp"@{;"y0Sr/:Hk5$ cn 'rѥ[jYUm/2P].bUq}lf0[>j $ }R"!%~ ^ wz$ŀ&P5?p7 +EV> ۴ R'G1Ue AS|}_Õ0>?tT -mvaǚBd@'a*[u!x%×'V(ƘvbRXs':DG;dƞD5Q9j. p➘5zLչ54;-:sE:o ep"`̄2V]nQZo28[KCήvД!AY#FFө0HtOwK^dC! n?k ުbD@S ё[l&5{L ވ16;"HEE\V,-99n8Μ2ukڥo6*l0=v&3vz|VdTփ:O꼥KRXRpo tj t鄆 'VsJVs ^}LPո*>敀\Ϝ ;!tg=l8Z ~VvD _aÛ`#Q9!G:%ci囵TbJlp~ꏗ߇N8&h6}5ưyjԗmz>úoct $6uDLXҥgIIaO {G;GH0aCA7, A:l]?"DryXE;]n"ZjZ8w>QJE&ۢRgURdDp7: u)Alih\rY,WXB'#]%wZ|H"~Brvl?-:\E*7iP ejVl[)6ݒf #m`јxZA{e>p.-f蠖q]aJDwt?նǦODJJS~CbcgQ9l`Bwd!%!S#u,i'qJH)Sxfp"AK wIyS`wXJB Po5R8B'M Dq'<1;˃MŨk~#R yy49;l!#ko9~(5agbJKؗ!A+$c'B'IIb7(JSf)ZY8*(ɏD[$MؓagzIjO3+" !L ZeM̂=)@vHpv!E`AMoE1]J.ϣ2 !:;ýB-d&+[`7 )@N2,䊄>P$Czj.)0f4+";[j);Pɲ.A>"A9R qK&lHt= V#UPE ng&9ƍIsϑw{p \'.Ĝ :'%G(aFu|XU|U=f; |̤33@a:b& mKU؎M*'ij"ͣGi~B# l\H{&>h#s9H1T벾E[ۉ.? v>gȯ|^FB#6ًI'[@wGA6ve>콺{A *5N *yIfwVhkUVv]G?a,sl >W,3?LED+ #*"G,g,'2wG܃9ByU|JτlEKϸVH>X9ؿsXY")1k{smooc6m i-aB>XX f9!~$!6+H [ B*7Q"Y^tPBI PuFQM%uztdXR !W_RMp"1IT^|[.i|Wd*@=h&pjL_j7MZԸA8{U=]BS27EmNoᦾap梖(gŬ;t\ TFz Cex!LM S>| ϐp|08F\kG ]!LNVRWf! Y%;d$yCÂH7$ܼ IF4H9򢤏<6@2{s!)+یS SW2݊اQE3 `ޏz#K,;.(#WlC aU5r(5]as%<|=ѿ@"M`b8 &d1D{duDi}l<UO4/Pl(ؓ"JpWp "ԧ,[[ ƾ/:?K7 w֠D;Qdc%AĘҪn B/=f<ĐA%s be" ȩsb%R5BMN0+}PEx: Ǻun^4-N!/P!49k l ]@AhHPC؃E+ \ڶXARѬPA-*eK|BKJP6dIs#x+ļscDxU^S1-\[#o8sÎd>Xâ۶ڛs>OX8aZh1 oCތ`VYИ;J;*Fhʏne\kcZPv3/޹R{s@uB, T7U '=6U./ ˼YOa2nV=alU3w(◄os.cO䀰. LG;'}@/I^edUNXxgfQۃMaYTZ}7pX;U\ tgl*Khf9 ;7,zo@ B5Y000? = Ce5[M݅7dCPx$sD6CL:"R$*hBG(^̌B +i"l8"l;JܑU88ua*;YY#POVs:yi`^UFe{qfkZo^W-5 kj}T*sdQΤԨ('2UD)o0n ZRR35 NQMBM<$<8v*cv$_D :I ^ IJЮpVaۇ_ዯF^?FV>K0NGr TsRKdm;..cqU 28&'b! lpNޔL*ESڙafߴ). x~I;dk`!oF85EĐ/оu,!|'pˎGɋ*Uti{ ](n]\a*4'4% Y-ڗ/x_D2v:Bxc5 Q(VZ֘X~',!+2*,m؃؆ȵ=1ĢqE6""bn?h]^ڛV[#F 39k ASݒ1!]6+v+]˰]`V""wCw9֌3abL_j&UAg±>nv`֞м UZ25ObeF C::9.3+v$"r8`wLwPЦXX\Iwp&^'ʜkD5ڴC  㲋F&Z7|tώQǫ,\ѭ 6bO:3oq' YDFɤ ;=$D۾ xXM4mlccՓL}aH$)¬j~fi"8R D>]%½$"{ݻLJ=-g3,'t=Cj4;gszIy0笘FO[p &LYy6 ؎vyoHgIzhSOѩG9BY qbbTlBŜs\H)SAd_{ĺ=A슧shG7€oiC{(a[%I@eeؕoSח`B|j9 .(F[m56ؼ)]%4e}{aZ-b !R9xUPfyŝx JnxT$ 2/C&~ Aa`+ڛ4G%x'impZ>+Jfvͦq Na~rʼn0||&H:CUA}=M7<3Z4N/O\4)KD4()$%IP*3QpD@xp6s!yx%OZ +L\ؘb wl\aR]isHZ v*Msb.6izv֩=\{h 'gBJL}R^DSsoBxV07i}p!cs" `яоKN6Ŗ(zrT G 0 cuSbg%Sʳ#1D:|>+ݷo^?O}ۗ_ӆ|up?CgY?q'ۿ׿lmAz!颥H78Oemś{"F f\P(F9Fl}_n_vY귿f6*D2tFZ]'xVqLn8|! <>~c24]o "bAYfA 0 咍U()BP)cDXju7sL2'I?a{w0,<-.{POsdoo<)Q?+TbFkA,2%EFNTps#~1=;l;`$X$PD?IӖT \ʐ K2|⵮U),$\ز#\D]2b&Hmlw% DZBl;*lF' 1ڳȓ퍥 >J͉oп:Vx`}P$#"ٳW <_Lp"v1t1#SdIģE[~yDDn?̡jYpsv+u;AEXlkcUI *v]Vc`ceMJG||I,hg7I Jߨ+G`sD#~QI 'Mt;̒%KIa'qc9LE:5HvjJiȏl8<͢&JT?e_#qeW YUf a¬!=> aicXJhJ")Q<ۮVȬs" F܈Ȉ+n{yv 6reӉN_pnS9/fR:4m]m׶a.{WcJBJE]~D i|&fs/ ^o*6"yr! `j63 sm}8-("xNĠ̈sA`[fgͨDٗQL +eT.| 8Cfc}r%:۝t9tH+XEJjql|84f?=!p]#]%$Ԙ"e5&K<~6[wF~G 6(5H^-փN yf](qn 4T OT 9MjxU~hhG?BlĻ hdZDICYn+8ijDU9#Re6_;2;)4d*ОV8O@.:PIdA}2~y:p mL7]ՈTg*V 5G\Vvm%Xڝ aW&*Gfe c ZektwS{;DEmae(]I{`UVq1$f8DPOUY:o%PRnma"쎳{ʱv"0_3bmgZ`IH>DF~vs Rlj汭C<¿ =vʟCVS&7o$SȲ~7PPgS>W 0}bt󦥼=@Z )fzV[ϲ qj"{rH%nm\Oʙ+.!) U&4_s U{[tO5~umn!W1& uԓ0~A}OJ詝O@Nxo4gqL޾^B,œ^eQ} @Z|$QG om1/ ]-yyÛ;{ushZp/<\ B0dlhmS-֛(¸'`٭tvD˰MB rwɉE-l@o޾VGKMRbijT ipf%"ݾy[2+xPmL[ZE[ihLViCmzYr2@<D'pj#ȖNIC2 q` [lZ mG2 üR k5㲴۴u6(7x'ېl@SYE BdO%VKL%h,_N`-5 g֡K\d@\N *>O6X#{(lao Q'f0!b] A8PSLzYŤ %lVa @FVF > 4e,"#꺆zA$`"p@E|s%ahNf g /q9|ֻ1Y%Y4/2(2{DSAB_%֒{U CI-zDN! m%qH`{!nH1ЬΩ4yǩ >K8 ~S3e6IW #G!/5wǸON&2l[$;wh7Zx3 j4<9k@fo`g0Ғܗ@FF\yܻh&ByojlwymB^tZS\?KNEj03>Sf{!t6hp G7p8X`(m\6V,fKٔaL(7Q~عi6ĥ1,j Nþ(œ&p;d.~2oAךhGx(;V\fXt!f/E&yz@X IUHga+eCyDwI ogŒbH%qޖ5Q0$9D+L:,.|34l>A #Z $gB8Nb٨> kܴmdaͷ[Nfp!:к5RCw5σir bnѓFUl,LsiϴC[q׀Iiwe[>@[ˎizokf(?"\J ]-q8% A2q~H=j=4bX{<^:#dȵ7h6ek 5Nea,Rivb(o273 2(mwfSA>r6#(dwAu18Bb%GSe~>9fqu12 ܜxCbQQTĻ]c+ _K|Ac#WlmHu Jbv۞#.f0! J=rvH\U9T3N;("j2Kyg2ܗ+Ya\rXp~ۡ19޶bM:DH6CVa9#VX20FwJlРb0CUff!%;I_I_HE,0!`G{ 3˙Wu|tU[!yBDQJ#깪Vt=K%d^g~*ifWQ7t|D%-t ,LBt9l9͏e~),fs/F9kq޳"lS, tZ xi3 '2T#/"Bk$SnW.@4 B Ш7囟LSl6t҆y?Rzϰ^ti◱A5Vt܈.'0!:,#/sE6~/b\E_:]BUbZ](\{j` YpMw]/'eil ~ 1qxUd@i۔lˉnNI( fއq.$]y=UCvQaw V-+L&6FuMOpXkiQ2#cPr@r+JL`sյh3,g;siN < ؆`Cb?i+A,>]~J f=UdcA s 'υrg9[G93+V(eg鸎k p@l.z]6l#HaV:1ݫbJɉ 'фDŽ-bYϊW]]pw5X4zhu}'ÝZNv>s HwL+L8![LQŠX@6 hx\$"+̖" ͚sn3 E.0Q^ A xms8D'ۆJO(\kh*QN4@]U -A>o+07`s}XL GA79sk1sY⩸k\2n2Zu˸Znli=Ji#Wh?_W?=Ѻ^kDPGy Sf9p4; 0Ʃ ~|]"AJrw7rRzj6A/2ӠD >uqHIvD\ Xg kV]ƹ>XK$iŝr5Ǹ! :~˞K9ň e`n5>YqAsL̼^к{]e0{yF``f"㦧xHNԿX6 H+2c݆O{ZC74-}L,gqȋvƌhg|# j+' G\_5n\-+I>o;::&~ mB7=;Ȅa3c{S'vgv&b޾bvr։CRz&E& hĶ8?' eWZ9Cp NP_(4Xf\uBh⡁5ֽH:Mɲ9XL^XސnqM4Ali8g1"^H8iwOb6;CF"Pf9nYEqO0bz-a5VqGEbrZ :BWۈ L5`p?JBT]jmBkB3d65i)]CoIPsP{: 3P :i4OX<խh/pn , ^"TA %!I@*%HS A J-NGz-T~T:5\W$7H8$K/[V4V2̦4Ac=]T5W"1MDuXler` Z GhB \=z/U,x=$90hk亀_a[YA9jh\{(2LC -3lvnrRlXϒ[}`#Fbu u8tC*VzW` 0H'WUJ_fg}feܻ=lL\$f焴չ*%\ͩݒ[2k@}.ZԠpmR*V@/t쉱8Vo󄖦wC 5LSjVaJD/-=U &m *^trJq.H~gFx ϡdԾ5>ZjiYSRXr䀆<ɛ|nEͩhn0ļ txO“* 9xXT3ĩ4J0 2$@s0k(u>2y[HL3OQ@;'UG=/+! sS"]tx9_+oɋ&)eKRQ:nݣ0oZ@mKx@5oONc] 0]&^Ȑ; A@nN&b*=33:~صƛF7rhEr!&^B^@Z1Os)%is̘URkrKMH,RO}\*5S~Qf1LJ_P&L)qUl po#߄|>Exf(4z7^0_Oe9N4 ,ϲpzB ȏEAjQ[^=9׆Ww*^܋">-|iѦܼƍ5ܮIMU8Ť7=nIe7!g8B4RhV IAjScH!zOawiz1zy&3< '"'BX^jClDX s#0[j8hmyeɫWkW-!ѣEFܘk~>f-6d)7paU s^md"GF p,c}7D3ijj8(ScRi NLr~.5,D 1NRZPP^i2/vDN`TrbuZN,ww済c^xN/GP{ li/q;M;roWOm,6tJ6Uz2{hH ba S`U @#cĕJ@팖A*<EoT#myH>se&ɚBgQx$7"2ffh[˫q\4jij˨h X<7YQD2\j ϘHOddwc];TA67[|liWx6Fy-U9<=`6 7N!Y 9k7')ze^-X_" P=*kp,ChƼ!!8z/ae"9D[Hp2XSOkA:(v{o1bH,qe HdDŽz9"#Rw9} ah x&ͬe;wqy$ ;ZSEUa+@`CF"\é7DLp~2"Ϙ8˙ywzZ#rY] T),U@4s&g&i/˄B DJ`F>8AT-"zd{@# '` JOQ{H@ܡes!P=2^N$6~Ce\kDj^qoncςL#p9#+!de8m)ġqqJtQa6Y>P6 AA } WZw]CV<[MQѥ{GKF; JʁLL-f+{7 ,NR3S7nq ~^^u[M4$l&]9Y6OaLiscݱݿJLM{PwΥ,hR-w2)G:Ks&n]ýt>=mm,CEv5Z`c%HmzqrX} ͳ+tBrCPN|JQ[QRƟ/j595h{WͰɝStō*]O-i+warmS\p5Nh!X#D ':涛Е\u:ݥF$QޡeA/Rohذ Wh K{vwޖpoUToDpViprg4ĚfgE_l μ,&ugB̲nu}Gm/{["pfPg(6ٮX/|#vb6_@L|zhyΔU _C1P׿N#+ bwR(`<_ɍ] Bb@ Egp Q pexCrtYk#TLќ+Ԟ/ʢ-ʸid~1&cZz:ջۛgA ~{TBcݓBb@;0g}8nu-~|QjE^I*xx-bZ1[fA4-;?P7=9 }j&hѻ}bZ4 iXo;h}/KopȺ(G'p9b`6v[]vU.0ίR #\Wp!4&հ8bPߎ ʐTx 7]Y_8 +D#nv6GiJ)t 'o]3%@ttC?; R.FΓތBClM0o9H} x&ǖ-2VBc6l-1SC7^Knc]O+C_ވgL}S6* Q؈6Ȗ0Ni|F4U7<'Lt轔)Zf`24@Qٟ_ʮkd< 8; Dl"´80gЃd7Oj 5A rl %b#>z_v6s> f]EoQ"ߥc*fΎ׉n>8MHP鰼}\VMnW3+@Uxas-xkXL8 ;rԩ`K3ۃ$]_5hjru#utD R,T #ng))b0Bm }$YEyIo+L j jR4@F6p9vJZg3CkO;iyy]\㢗cð B,v`hm"m7ɮ؞B\ҒXP d/) C,hM0AJ4{`ڍ2 K2FDCf"_wnK#+m7c2  B[.HHXFˑxԃ,Ȇl@ZhVgzΡdVKm@uʁ9Ia{JD(Y$!|jcVXqk# gnʕ  Y-f[N' daX5h*+AHxҸsqnɷsuO!c t@sZ%8U#ŕC5."FfQL;!f]MB VXF#/% vz* ή9 5D*uqfx,oOAXQかr 9!-. &mTQkzÀ@Ut Z?ʿ}z?HCKvtqZ[$4#,sD8eh&rM4C$'hƾD'jIpWD|b4vZӈf(.>;GL+%D`Ͳ- ɥlXuu\F j45efj12HF*.wCS8Vԯ7pg@^5ٖ粆W0m&Q1*JJ5²p3KL<:x I\`!uulyCq5 Brr*c< Z[U?7)n;bs+f.ͲV05z쁹;l!<: aT0OkQ=r|Adꕙ6rdw% 705YtYXK_ͬ`]F\GC乫n@ygY 1aB Nay_x|v-[Bi_,̂H|0z] cy6qS91J[^e?kuhҐ* o9}e4j[ >[%vT\ZS' tj߳ ",k4mN3ElFTA.> N*td~\͇LMĤPEQ#;Ռ`T7J*lowXQvWﰲ Lmײ/axVPCv~|?}j?es{e_ILW/_/vǙV_Wwfj>(_fa0@Oż??AO_Z_!{Gb1,'/1Ηuϟ?3z\/ޤW5H> afzc̘<9,-?3(?^ya'3^qr bIn2@ 76X8œN)bOA|OJݣCC A5 bO"^[k'} jFR5!wy̶|;\5Nu Em_׆4I_Gl97^q !4ug<;R#砋fGq5 ?v!D[_QZK0e}<0qߗ' hp[nV8>r5 ]VRSӃ?s;NfQ\ )̆J@[Wa30GaCR jp5bD@J:6Zơr7HR߂KvJݯŷI9Jo܈\BX w*P.:-}|J2ҏpx^3Nq HeKS(_Bgρ?8$ryJ8COAu6;).y^~^K1׃yH=>Yf];tn&9<=~ #  PwuT;$7>%'!6+YɰBƗp4"}ؚ/0/GZ c'?Č?5VfiA9FXD]aj ۢ!Zw`2!z}CcFB 4FMG)R[۱gҝ/ݸI-xrckEubPZ>w GuWهn I۩YUtlu8Y 8TACD4E:M%*sݮG aKt,MF02cdj=V64MN]bmFx/sNK-׋UBʖNQrgu@ ajv2@;5|l4)d+'QJp^/͞'}ZԽTpf)69'Eoxtk֨'ǃF16[fڿfDrS%ړ(Chl]aEޔ;J9}+ ~]I~W};U< Ny}vƼDl*&yX 9Al@3$Cr￑_a;2mw oRԩϘOHc"U"2eeB @²H=HB~,os}ǙԲu gaAAN3:ir%{=_ם ShÇf,y*R4PS(^"C E ]Pw-u(IU:XWcLN^Nt\#ܩ&tHMy:nr+Ր.AB_=e Rǐ !lbyLRtT Y*Eb isr[&`LPP `v/+APg)B͆5K(LdEH 04BJrɎuѾ?.Hdg7,;f9Ї. >Vd*  Ҵ} 9*Wuy/\_*҉M FLF-۾e‘m###B`"c.@Y] ȠD)kB6D'c`9#m52㡇:z1dwT#:oB"!*hz ʩ ii#]SMo2e!"6Y-BQR#${?ҼQsGRim#,߾ w=vwv߽.ϑ}|S-o^;=-Jݼy͑W+<}>p)1m렺yY-WjIR4H+O4!aTG6Rky|݇bnk<4(O`pcy$Ct*DqN@̒e,6ϕpt[lU G]׷/9Ν5RXs?.DyꎐU. ={k5-Qp'(BPM1ed=.grTnL*iB,V*o^v4 ڎ}wVW7t\ $30$ 7B#u@:zhs5bY&>(yBa8{H *^r]"=b 4u{L{* wυPT=HoWW&:>h4A{+#T갂[vOG-'>D3wΰ%^)Lb#"NO4 %d*k`K~"YvzvOM1vDhN 2{dJw޼>& -J͆# Z=tf R+p>`̅uld :Z ÇHGI=m'v6DdwG I)zvhbl((/ Œmm~VㇾCll?F 2)=~ΫU6?Fæ"iy*/П+gAkw0mQCqS)q ~{JY*Bkq՛ݾd{j& ÍVOT K0I $Ș:Z"m~hZ̗DRG]38:7Eu pf Ĉ4(jbeN꽇%JGk[AQ^ZRBޖC0/T[K XFFevo jWyê-Șu:=x+:)ۗ!dYHQ5 Քb{(Ae ИzmEa\611螌~Q 0YRUK%E!I!CT'yVVJ2J` &;UŸ`Cp2,f!MBV*.zmUzD rO-E:d~ -C*|HXY!Q9R +K 6HȱU娉CSeBL99})7PWghʖ(,2 f>JJ_nQ "aA)AlU!ε:\FT,=rpGo6mڐº7v| C~曗Ji:E뮞 Nં@՗6νTNK9ؾH*dCm H5^iš)?l Aǖ>2f.{x`[Ucg~ڦLcv5,M}*ίBIu7:ͽU s5 Fpd"g !`jPd*Y|)\Tp_k~ԾY-sޟj _-Ze!i*h+:gM;:|J E0f.O҄ːhܵ``B;p'Ym,-kk}T^,$SBQCȶi˘P6p 5bIʨ WBS 9b]CtOq;BOEf\>=ہE/*Yk.CZ ,崁\-(o)1jȎRUTh9^갶^`+/Vlrr.]*P^P-h۝ŭP8jf8ca0E6vz ]Ap% 763nr;׈9t0]'Lgb>Й}rhłDNߵxp_d * CK͎ 4/@=Dy||Qb3iMN+7Пdc%õU%ZF~(#iw``'B}-BũZnSt`Qrt FZH@Yǵ+ω>\(I 5!w0׭7b??u~jX"tiR{H<=-rd=OX\_'?CAM'T-'z6@Gj= < x>x'fS|'H* l36y4Gq86+W}؊_S[`@qD rL -+>Y]%QmkYdV(N*r'c9UU%s*[2%OwݧϭO xN"V@ YxuCc_ غnr-`ЈHCPUV}0IphaW+&X k0֕%eE"WD9Su0T5@:F55-EklPvHsF2{WU/`d ` [o7_!VR>\YԢ|jB'`*Fi`Wx:D $ԎT1 ޔ٭V9vx0gdU ;7P-䌃>YÂ1JfQ+ʈmNL]`oײn%|M N[ݭ7=pdtR@t/5%?H1#s\1zl\ Ks]@I)jt/v6{;D'5Gn@R\jCħ N-S#FǽؒஅF&"ꈔ "T[7 uh$Jmj+f^z uQ$48KpFէp7SĺF]=QoM/ՁFK]ZU q>fۖhlR؁b rB C>k5KXjppd;h{)yL کY۬;$>hkt JT![m.IlDJ&KYNeV7BO Kտ0mimSX;z^-m:P_l ҲAH3bWk[m?B2y ݨv%87qaOKxoE| )G* "O{'vBPtY&DƳ(~[LF&7~<+"Ԯ* VI'}!.d^!!yӒμu\; zny؝b1ƳNQj@Y$6EN$ C&̖cJ@2pS |$RHZ'' Q{⦶o77S3'}Mӫc^=}cݦk  OӇv`%E V:ak,TQ5qTO}{8_"sZ ycB膙;HIް: -9_:z?U{$ՒZYUHMz_2]_9";O ͈M\3%ȅ9JX"7KbHDԤ؀mJ`D j_q;,qꉷ퐴0Ҩޏ)ɢ'ԧKNzB̗ɪ怦J~ūu</oPKt[Z]w*UX" k3/ TqĿP 1M ,JB`(9u3`.zw&iEdƇJ vd8jwӁ*:.1q46E7|G aū@)4/kunH<nKuh݋jD7N-*pl m5lv[S9-/ʝ3Xg1;`,Ȋn'6()86wqV@!-; [B¡Q+v<8^X!LXJRw(&#lSֲ <jo+.MIm&8 M巵9ș@Ɗ3A+TS"Kenx]U[E&$:ĔZZ2la|Ȋ2 rò,,iřlihѴhA:K#a5< "#` ׅ5&i Q,ET@:r*jg^$Sy^?SdL ";K~ fmi @OTi^%BjzǷJ4!aNƛE=7L+jpܘ&1ҹ__WXxN/NeYPE2Jx[-C`oiJwq|j7XA.q%K6u5j|۬ Nky]ET'Jh>hhAdeu4m6UK8}Q" ^EsS6rE(!mK_ÉAtQs&2"qؘu#%Z ?K2&l" P1 Е+-[R-!bV~Bw{) \|BܢX]*撢h$_5t8b;E1f+gUؙ:0Zpk-F؞Ski)B{bW'&6f&[ж.Jaz-zB.^{pNA~U m,tsL {Y$zHiWLhC,{=-HuO$nL5# ݗBw iUnY޴ڌ.Np|ǎLH9K_|B#7,AGx Q$2MR+At^Ekfh 3唿_i輦ny~2JrHkU]g\apӍ+TǢD1]-rnܸb@e0C rZ?͘kaҠ^_! ,qhL+ ̈́a{:@PL"v޸FѐLNzj) ʪ՘_Tӛy}Sz")w2|MSVWhC\ׇ2몴=ŷ+ޚfb5օ%hjPl"G jY2Dd p1nPXָ 8OFiAC_Ҏ5` I/*>6a1Qw(:D$p`VݠHCt9E\k6Qɓ1&,it=Q+xMSZ쓶 lvwJn|!y]UYwVaOIh ffx\Q"bVE"]r/*LDOLJ~aj3?IIƯg"=w;edD+4^/Hƽ~7,@ΐBQ@I?Gَl~(j0h5_\:D ӳwOq`Y܏1xa8 +`v'K. .\/]稕U%s"VK_u|=P`ToZ sqpXg4R&AB%m0p1C@CAԠF=p 15Pdd#'vxh֜9bI-QSnbiMZZ#Xӟdrmϰ=T ݗ]} Ier$6bI k괋T0K-j-!Y"r.Fb`UM#n# '@B̐1FƛAb1U{y4b!QeUSi%D}ҸgWGjB!$N b "PF4 .#3b-q6e͂lIMDy>ߤ0oqY싢^MAPf^,W"M, &Q -9Zk#ׇP;FɩfW"ZV(6e'u6-OkxV\GKԜp]c+:}|p)*Th8QKI~ϽW~V/Tta]ao۫x×G=A^>oRWR8n`MsFLdNS;Kv`yt p ZZ p0ye4YI5h0L:`؟ J|g0.Q+UA7s2_\v cpCLk$ki0Hdo|1 P"@4L.>gN+];ݨ b\f[@+ج1lSe(vܷma}A{eEϑ9 2 ;2eT(DѸgFؙU+)^x:hKjT탎?lYrߎ2?J`é2Gf/LE\ޗ&s~4OcjtIu6`5Wj?л؋ gzhG11cуBiA?~rȜQvdR_0]r~ڻ2nf7nhȧ/6M2ً́qQetJ.G 0?yrU`z-(!'>ȫ~Z߹_-a$Cᰃ^~o5H.Ѥ__t0:P; 18G+9qj[pj{䫫~4{aa79G{iu.l|U SʶOO_j,ʢ=v|7roa8~ j#Ph~ߟH*j;-&4\ ^W!tGS۽Qj'O"yފ/%kF[pSk{,mT=Z퐵Vej?rn7mCިJ\"dNf09(%µGa$&8TvzhhjI}&פ(_iz ԇb!v#9CU-Nti&*&lKv0R/]cz<4l)%3m&jʌ @A^(H[W*=#犊`dI2po|c> /{zrɿ kk? jR`wίOAOwKwΚ s34~|Rl’P)6TI ^֋L#3VKBdWz43vsNeWW" o-Gg1ԠOԴ1`M{zz0$fXH!S]R|]݇H߾_``*LziNF^{d?ȩfΝ hAF`\6+BXԠϥQje5bN&բ‡\be'- <mnb[ fl({igΫuJT_vΙUy3Z7U7swr ;a? ~RwU.;֪wB:R˙LwY^πZ8xUP ֕?b]*@s5~r_A.+6636mCnQ+M, ' ~dFǷ-ҭW8^w⣫W I2eџomZm$l@Wsb~RoFG#8TF/$ A1$,$y.@YIW>spxJ@ڮ^v=&YsT_UH2RGe~ i֯N,/G0Q$*S+*(V;nZG"j\Ss݈ Gܪ+ 'F7d{WL/$9 ]"G5YܲN 35䖁xGu9n[#bB%-YNƷTZf+;o=:xSKW1٢V<-^gI Y< c8ϭ`@H,, FV6L>_gqMo~(6AmV6|cHO\t t8IWpl^< 6zƪO9y5.#5G;nqƫ nvɫq.ti'9g5Z8g3eQZYqdŐN>?K?-z.^,߲%(errlǺNFeX;@xf6M$-D{a5!H%_dzBzi]D53ѩNaA 6[h~uĞ͸6N)y9Ψ/- JgQMt!)uNwҒth;o\!" k~7% @$:f_pQE_KJ'tҰlJ+r]sY ĜqIAajvI@ZI<mIERPʻ?ݞݛ#:00sg) gCڏ?p D*b9+TS8ۋN~X)-I*$*CKjg+&7BKslPbJoMW)Ee>NW."4Hwj8̹2ڭ}m Κ,WST%IKBs8:~B`ov&I$`V~^wъgq;Վ#5㏲{{!geT.V< Pll>[\Fa#r0Cj yꇁQ7K9,Fg؁v'7HD.>ݥ_/xwjwi`}2lche;)a-,7eKFYg[2 F 귰-Cp9V\aQsOȤ, `f%Ou|>$\mylV>}?GEBzl!8'5zݣE&2۰xTH- LNV7<,%F<'ݶVw4]֡NBЀnV P *.w]ƀ)Ҥɞz7k q2Э5?I DƋ:"ui*y{i+LSx\&,l?p MVuuRst1H!J ԓ'I@CJVHz?^ PK.Р#;4NJ6*XY <=R7l8rd8k'mBWkM MAhTt[kz IIic]_]`LʑLKm -J,AVYhvI% \ rzJ<rn݋776{`U4::ywLsTL4bG\䌷?r9z"T?;6&)x+۸9nhp9Z?֎%.d6=*ž3c D}*$zѮz}u׾C‰^a!fQ֩?ǟI18MȶXt[*+TV@ VDOeP@7ڦ K~'Z~#ѓ)V@xu%BzT:3Qwo G34d,UGt"SԻ+vU|vJ!ݙ`wI{GkǩMC>w[&QbvX[g[+#"Y>8G|3V"_ Zut7JNR5 [ ,N uUQDuƒ׭כ\67_F]_PIt+U'G\jv6 K n+3AYםFIg< PStO&?PK!=dxl/drawings/vmlDrawing1.vmlTQo0~`yMN}&m0U qHCvv@w k kek')[I j߿/$l6"N5ԖdY't`LQl^e( >H1}űʻDF3 ~gy6h7Ϛg,OΈbĪئ|6z=4G'a =qXkM1j%m+Tx,8KpN,xKz^8yϳ%g+yQޠ'ڃݡ jnXAn[9~_fw%)>VӄX"hn m82)a ~gմǙ9M i SaD0"t"-(lc2(oNG|Jو˧hs/nlXϴU 8,{:`uIp~#| -|>fYfc83|ڋP"&EdVZ#I2{V4|9ohj-Br(0P&WhPK!K2?hxl/drawings/vmlDrawing2.vmlT]o0}`y@I:7A:mMTU&6ĭŗ4:N4${ k mekg)[Y j߿+_^Rx9ѣR[srr[d=\+C:䍪e*ÔDY+eEH5U>=fIY *ou"I7gɡbĪ|kmzN5{<,u'c4JbV*T,9]b- p`O윳8ĤoЀrnQ_Rl7ͬ^8='YWcՓQX!SiP/N.yfҽHx2NTF)&lm-B[pINߗgQOikArp75{v@Uk ~gdQZZA9.ɉ+iIaL:Es XL/ }7'Гe>P;1^gm@?7|=^|N2Xhk}$?o]YAcbN)Kw) |ދxMnI孴^HًZ>xC2]!cMпPK!C pxl/webextensions/taskpanes.xmldj0 ``t_wPFK=(ilKkڷ趣,O/qRg(Y0U . (:7QB Wdطeniv YhaҚqK)jQ/xċ`Z{YMmjm TGTR9 :0Z(%t2ZؚgP 9iT)G'\QnψI ꌓU=KWbz vW3_PK! ҟ:-"xl/webextensions/webextension1.xmlAK@!=]MZMۢm2iݰ񿻩Zċx޼o:wm#TJo3^=褮dk4dHftl`@Wi'b@'1Ti E騩kU=Dgs*#qy\܈0q(4 YE!D?I>ıPIk#6'& /R*74`VM%țHepuP`.GVA]P(TSzfZ_> ŇmPK!)xl/webextensions/_rels/taskpanes.xml.rels\n0 ;aBioH\'!uۈ&h_QVKY8ڕ#xw7%Y|U(RQBJÉbL-3&nv&l/̯SG\ i !G@pMZJΓǽNmu7Zz[q[2~9wx2,[&TK<}.xb;Yųh3y{/g_gWԚ]8$}2e' N?)mJx[4L4 d^^{,cnh'pE?8V nBez̩}Oi|cyP7z}d#Ա2pz QXg[g~@yDKIY̥4- 8dPJẁ9PK`]D+ndyH~4JHM KEYe]kѝ%?kTPjj /h$F:Si6ԵTY+5V-؛gn^c@uw멤}Rel)Ɩ6k"F>ܨw] Chu,hcJE꺃s}Is/l,vubYo=cq -Qv^m:"|ho)n v%/yЇ1g̱"Bx)҂$ٴ`pZʚ[ f,j 5_Ũ;ݺ~ȑ5Ą9AT^|Št2eD U<,Z5jBJnj"ąd"&T8CYs[6], i2jcUQ U"N7@`W :[ߠY&gI d,F>6 )۬VW 4+C]Hծ8}*׻JQ@#(wSڎl!(Ɔַ_Ea>|\ >ꦯݰeee<\[] 3'm鷻.bkQ-բZ*QQWKZȨjE5Lc5#\Yj6ͪm,./FQM0V2S%v/h%1 ,'bkYz\9@WZktM+ c&doE},sFv|a}ךZz5}.$07R+=Oץk{{q;m~ݗ~߼~6}d7%Z=A,#|U*?$ ~<&wPnJKW PCyB<| uI. ]]Di:6u+GqxbU=m~JїFu|Dx:/b Mx݅_K} kDЦC"EKk񨦵Ŵ_kD\41^ݟ0^jʑf(ޣZ`lLkst^r-ޘ2_M:ȝiцWqM~ʳ 3?[Oo?Im?Oj==\.ywquwUF{kȃ>|ël}+̱BλWOD=LCovV,RBs-|=M1VbWyxGm]zZ.X#p7#+4Σx?,ڑ?y_a\]RYkw 7b;тPMhˌN{_O1c^Nj9L!t4w!>W7agFvx9|~~o0h&*~leQx,?-b"'80C<XuaKk]xC]hc}7#}œxxzσx <|1m  >Ob%ew'ms_PK!,xl/comments2.xmlRM@ ĜdčȒžR&;323|P+7I!e}l==?d M.q"]Iv\|{ݎBTRA`Ybp!Mm&e(*EojVE.^֣BI2Vi'.-?booU;mt<`yȫa'>wVq@Zd*=t( pKXdx&6Y2J6:$t0U<ֹ2p_#4k~YL:kk\7j؜J4c3uI b{:@3` <#1ӖUa3V9TYr6$N ݢLJ6*-%(k)5xr7mh^Kqk&+3S{9xWP֨7Ypc!ݬpbo.c_8FCc)g^]]Z=9Eq,H7!"f$OIšg6Hȕ*rڐV7e,|G[Nc\]^ǮͷċoPK!Zahxl/persons/person.xmldj0Dݖʎbb(=JXĒVMJ.)aV "kZ:5}}i3I &^麹[S&D&ǚ)KP0t)uV12$7/$Kfr9 長<~ՅT< W)i׏.MT<:BĚ~IY9kL3Yͷ؉ł[^~S'߫Jͅ.bNɚz> (kVPK!p@docProps/core.xml (͊0$bô2;Uز+) C HO{; Am⃐$ScJnPIN`f`McT|=ǻ8ǜ`rߘ]y1ӳW:K͔<=PK!¤>docProps/custom.xml (K0ƒ"ŒI2L2n,]M dl%m(U )t㒍w.WgrgG:)weMG,QGSt޾t.gIS-7;ht}qktLej` 3Bƹ914#~8Ts?nNmM~2NץX,Vj)sFDlVP֞ʼn`Rk 5nڃMco?ROlI9J(a )ߐŧPwz[ٛ\ւӜK&$-a:8#., #<])>Ran;8kMLCM</R*"QKfc^H&)2e|L99.9OӳPK-! [Content_Types].xmlPK-!,t -_rels/.relsPK-!vnhtxl/workbook.xmlPK-!  xl/_rels/workbook.xml.relsPK-!{-ؼ' /~ xl/worksheets/sheet1.xmlPK-!$L?xl/worksheets/sheet2.xmlPK-!]Y ]xl/theme/theme1.xmlPK-!'8= xl/styles.xmlPK-!cA0xl/sharedStrings.xmlPK-!=d{xl/drawings/vmlDrawing1.vmlPK-!K2?hb~xl/drawings/vmlDrawing2.vmlPK-!C pxl/webextensions/taskpanes.xmlPK-! ҟ:-"0xl/webextensions/webextension1.xmlPK-!:2T#xl/worksheets/_rels/sheet1.xml.relsPK-!X#xl/worksheets/_rels/sheet2.xml.relsPK-!)Hxl/webextensions/_rels/taskpanes.xml.relsPK-!+,Oxl/comments1.xmlPK-!=8(Txl/threadedComments/threadedComment1.xmlPK-!W 7'Ҋxl/printerSettings/printerSettings1.binPK-!,$xl/comments2.xmlPK-!;m6(&xl/threadedComments/threadedComment2.xmlPK-!Zahxl/persons/person.xmlPK-!p@docProps/core.xmlPK-!0FdocProps/app.xmlPK-!¤>docProps/custom.xmlPKǡphpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php000064400000012221152427537250027311 0ustar00getMessage(); } // 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. * * @return float|string */ public static function principal( $rate, $periods, $presentValue, $start, $end, $type = FinancialConstants::PAYMENT_END_OF_PERIOD ) { $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/CashFlow/Constant/Periodic/Payments.php000064400000011323152427537250026775 0ustar00getMessage(); } // 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( $interestRate, $period, $numberOfPeriods, $presentValue, $futureValue = 0, $type = FinancialConstants::PAYMENT_END_OF_PERIOD ) { $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(); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php000064400000022220152427537250026770 0ustar00getMessage(); } // 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 * * @return float|string */ public static function schedulePayment($interestRate, $period, $numberOfPeriods, $principleRemaining) { $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. * * @return float|string */ public static function rate( $numberOfPeriods, $payment, $presentValue, $futureValue = 0.0, $type = FinancialConstants::PAYMENT_END_OF_PERIOD, $guess = 0.1 ) { $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(); } /** @return float|string */ private static function rateNextGuess(float $rate, int $numberOfPeriods, float $payment, float $presentValue, float $futureValue, int $type) { 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; } } src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php000064400000002354152427537250031204 0ustar00phpspreadsheetinterest = $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.php000064400000017346152427537250025210 0ustar00getMessage(); } 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( $rate, $numberOfPeriods, $payment = 0.0, $futureValue = 0.0, $type = FinancialConstants::PAYMENT_END_OF_PERIOD ) { $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( $rate, $payment, $presentValue, $futureValue = 0.0, $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; } /** * @return float|string */ private static function calculatePeriods( float $rate, float $payment, float $presentValue, float $futureValue, int $type ) { 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/Variable/Periodic.php000064400000013074152427537250025136 0ustar00 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($values, $financeRate, $reinvestmentRate) { 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 = self::$zeroPointZero; foreach ($values as $i => $v) { if ($v >= 0) { $npvPos += $v / $rr ** $i; } else { $npvNeg += $v / $fr ** $i; } } if ($npvNeg === self::$zeroPointZero || $npvPos === self::$zeroPointZero) { return ExcelError::DIV0(); } $mirr = ((-$npvPos * $rr ** $n) / ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0; return is_finite($mirr) ? $mirr : ExcelError::NAN(); } /** * Sop to Scrutinizer. * * @var float */ private static $zeroPointZero = 0.0; /** * NPV. * * Returns the Net Present Value of a cash flow series given a discount rate. * * @param mixed $rate * @param array $args * * @return float */ public static function presentValue($rate, ...$args) { $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.php000064400000025261152427537250025612 0ustar00 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 ''; } /** * @return float|string */ private static function xirrPart3(array $values, array $dates, float $x1, float $x2) { $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; } /** * @return float|string */ private static function xirrBisection(array $values, array $dates, float $x1, float $x2) { $rslt = ExcelError::NAN(); for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { $rslt = ExcelError::NAN(); $f1 = self::xnpvOrdered($x1, $values, $dates, false, true); $f2 = self::xnpvOrdered($x2, $values, $dates, false, true); if (!is_numeric($f1) || !is_numeric($f2)) { break; } $f1 = (float) $f1; $f2 = (float) $f2; if (abs($f1) < self::FINANCIAL_PRECISION && abs($f2) < self::FINANCIAL_PRECISION) { break; } if ($f1 * $f2 > 0) { break; } $rslt = ($x1 + $x2) / 2; $f3 = self::xnpvOrdered($rslt, $values, $dates, false, true); if (!is_float($f3)) { break; } if ($f3 * $f1 < 0) { $x2 = $rslt; } else { $x1 = $rslt; } if (abs($f3) < self::FINANCIAL_PRECISION) { break; } } return $rslt; } /** * @param mixed $rate * @param mixed $values * @param mixed $dates * * @return float|string */ private static function xnpvOrdered($rate, $values, $dates, bool $ordered = true, bool $capAtNegative1 = false) { $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(); } /** * @param mixed $rate */ private static function validateXnpv($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.php000064400000002506152427537250025555 0ustar00getMessage(); } 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($rate, $presentValue, $futureValue) { $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 float $periods The number of periods over which the investment is made * @param float $presentValue Present Value * @param float $futureValue Future Value * * @return float|string Result, or a string containing an error */ public static function interestRate($periods = 0.0, $presentValue = 0.0, $futureValue = 0.0) { $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/Securities/Price.php000064400000031603152427537250023324 0ustar00getMessage(); } $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( $settlement, $maturity, $discount, $redemption, $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( $settlement, $maturity, $issue, $rate, $yield, $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( $settlement, $maturity, $investment, $discount, $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/AccruedInterest.php000064400000015405152427537250025350 0ustar00getMessage(); } $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( $issue, $settlement, $rate, $parValue = 1000, $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; } /** @param mixed $arg */ private static function doNothing($arg): bool { return (bool) $arg; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php000064400000016075152427537250023521 0ustar00getMessage(); } $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( $settlement, $maturity, $issue, $rate, $price, $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/Securities/SecurityValidations.php000064400000002052152427537250026263 0ustar00= $maturity) { throw new Exception(ExcelError::NAN()); } } /** * @param mixed $redemption */ public static function validateRedemption($redemption): float { $redemption = self::validateFloat($redemption); if ($redemption <= 0.0) { throw new Exception(ExcelError::NAN()); } return $redemption; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php000064400000013373152427537250023344 0ustar00getMessage(); } 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 * * @return float|string */ public static function interest( $settlement, $maturity, $investment, $redemption, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $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/Constants.php000064400000001046152427537250022115 0ustar00getMessage(); } $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 * * @return float|string */ public static function COUPDAYS( $settlement, $maturity, $frequency, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $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 * * @return float|string */ public static function COUPDAYSNC( $settlement, $maturity, $frequency, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $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 = 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 mixed 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 COUPNCD( $settlement, $maturity, $frequency, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $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); self::doNothing($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 * * @return int|string */ public static function COUPNUM( $settlement, $maturity, $frequency, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $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); self::doNothing($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 mixed 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 COUPPCD( $settlement, $maturity, $frequency, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $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); self::doNothing($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()); } } /** @param mixed $basis */ private static function doNothing($basis): bool { return $basis; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php000064400000003774152427537250021555 0ustar00format('d') === $date->format('t'); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php000064400000004631152427537250022555 0ustar00getMessage(); } 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($effectiveRate = 0, $periodsPerYear = 0) { $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/Depreciation.php000064400000023227152427537250022554 0ustar00getMessage(); } 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). * * @return float|string */ public static function DDB($cost, $salvage, $life, $period, $factor = 2.0) { $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($cost, $salvage, $life) { $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($cost, $salvage, $life, $period) { $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; } /** @param mixed $cost */ private static function validateCost($cost, bool $negativeValueAllowed = false): float { $cost = FinancialValidations::validateFloat($cost); if ($cost < 0.0 && $negativeValueAllowed === false) { throw new Exception(ExcelError::NAN()); } return $cost; } /** @param mixed $salvage */ private static function validateSalvage($salvage, bool $negativeValueAllowed = false): float { $salvage = FinancialValidations::validateFloat($salvage); if ($salvage < 0.0 && $negativeValueAllowed === false) { throw new Exception(ExcelError::NAN()); } return $salvage; } /** @param mixed $life */ private static function validateLife($life, bool $negativeValueAllowed = false): float { $life = FinancialValidations::validateFloat($life); if ($life < 0.0 && $negativeValueAllowed === false) { throw new Exception(ExcelError::NAN()); } return $life; } /** @param mixed $period */ private static function validatePeriod($period, bool $negativeValueAllowed = false): float { $period = FinancialValidations::validateFloat($period); if ($period <= 0.0 && $negativeValueAllowed === false) { throw new Exception(ExcelError::NAN()); } return $period; } /** @param mixed $month */ private static function validateMonth($month): int { $month = FinancialValidations::validateInt($month); if ($month < 1) { throw new Exception(ExcelError::NAN()); } return $month; } /** @param mixed $factor */ private static function validateFactor($factor): float { $factor = FinancialValidations::validateFloat($factor); if ($factor <= 0.0) { throw new Exception(ExcelError::NAN()); } return $factor; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Amortization.php000064400000020610152427537250022617 0ustar00getMessage(); } $yearFracx = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis); if (is_string($yearFracx)) { return $yearFracx; } /** @var float */ $yearFrac = $yearFracx; $amortiseCoeff = self::getAmortizationCoefficient($rate); $rate *= $amortiseCoeff; $rate = (float) (string) $rate; // ugly way to avoid rounding problem $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) { switch ($period - $n) { case 0: case 1: return round($cost * 0.5, 0); default: return 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( $cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $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 = $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/Financial/FinancialValidations.php000064400000007063152427537250024230 0ustar00 4)) { throw new Exception(ExcelError::NAN()); } return $basis; } /** * @param mixed $price */ public static function validatePrice($price): float { $price = self::validateFloat($price); if ($price < 0.0) { throw new Exception(ExcelError::NAN()); } return $price; } /** * @param mixed $parValue */ public static function validateParValue($parValue): float { $parValue = self::validateFloat($parValue); if ($parValue < 0.0) { throw new Exception(ExcelError::NAN()); } return $parValue; } /** * @param mixed $yield */ public static function validateYield($yield): float { $yield = self::validateFloat($yield); if ($yield < 0.0) { throw new Exception(ExcelError::NAN()); } return $yield; } /** * @param mixed $discount */ public static function validateDiscount($discount): float { $discount = self::validateFloat($discount); if ($discount <= 0.0) { throw new Exception(ExcelError::NAN()); } return $discount; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php000064400000011140152427537250021352 0ustar00getMessage(); } // 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 * * @return array|float|string */ public static function fractional($decimalDollar = null, $fraction = 0) { 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/TreasuryBill.php000064400000013504152427537250022564 0ustar00getMessage(); } 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($settlement, $maturity, $discount) { $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 mixed $price The Treasury bill's price per $100 face value * * @return float|string */ public static function yield($settlement, $maturity, $price) { $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/Logical.php000064400000026626152427537250017642 0ustar00initialise(($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(). * * @param mixed ...$arguments */ protected static function evaluateArrayArguments(callable $method, ...$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. * * @param mixed ...$arguments */ protected static function evaluateArrayArgumentsSubset(callable $method, int $limit, ...$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); } /** * @param mixed $value */ private static function testFalse($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. * * @param mixed ...$arguments */ protected static function evaluateArrayArgumentsSubsetFrom(callable $method, int $start, ...$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. * * @param mixed ...$arguments */ protected static function evaluateArrayArgumentsIgnore(callable $method, int $ignore, ...$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/LookupRef.php000064400000040202152427537250020160 0ustar00 0); } /** @param mixed $idx */ public static function isValue($idx): bool { return substr_count($idx, '.') === 0; } /** @param mixed $idx */ public static function isCellValue($idx): bool { return substr_count($idx, '.') > 1; } /** @param mixed $condition */ public static function ifCondition($condition): string { $condition = self::flattenSingleValue($condition); if ($condition === '') { 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); } /** * @param mixed $operand * * @return mixed */ private static function operandSpecialHandling($operand) { 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; } /** * NULL. * * Returns the error value #NULL! * * @deprecated 1.23.0 Use the null() method in the Information\ExcelError class instead * @see Information\ExcelError::null() * * @return string #NULL! */ public static function null() { return Information\ExcelError::null(); } /** * NaN. * * Returns the error value #NUM! * * @deprecated 1.23.0 Use the NAN() method in the Information\Error class instead * @see Information\ExcelError::NAN() * * @return string #NUM! */ public static function NAN() { return Information\ExcelError::NAN(); } /** * REF. * * Returns the error value #REF! * * @deprecated 1.23.0 Use the REF() method in the Information\ExcelError class instead * @see Information\ExcelError::REF() * * @return string #REF! */ public static function REF() { return Information\ExcelError::REF(); } /** * NA. * * Excel Function: * =NA() * * Returns the error value #N/A * #N/A is the error value that means "no value is available." * * @deprecated 1.23.0 Use the NA() method in the Information\ExcelError class instead * @see Information\ExcelError::NA() * * @return string #N/A! */ public static function NA() { return Information\ExcelError::NA(); } /** * VALUE. * * Returns the error value #VALUE! * * @deprecated 1.23.0 Use the VALUE() method in the Information\ExcelError class instead * @see Information\ExcelError::VALUE() * * @return string #VALUE! */ public static function VALUE() { return Information\ExcelError::VALUE(); } /** * NAME. * * Returns the error value #NAME? * * @deprecated 1.23.0 Use the NAME() method in the Information\ExcelError class instead * @see Information\ExcelError::NAME() * * @return string #NAME? */ public static function NAME() { return Information\ExcelError::NAME(); } /** * DIV0. * * @deprecated 1.23.0 Use the DIV0() method in the Information\ExcelError class instead * @see Information\ExcelError::DIV0() * * @return string #Not Yet Implemented */ public static function DIV0() { return Information\ExcelError::DIV0(); } /** * ERROR_TYPE. * * @param mixed $value Value to check * * @deprecated 1.23.0 Use the type() method in the Information\ExcelError class instead * @see Information\ExcelError::type() * * @return array|int|string */ public static function errorType($value = '') { return Information\ExcelError::type($value); } /** * IS_BLANK. * * @param mixed $value Value to check * * @deprecated 1.23.0 Use the isBlank() method in the Information\Value class instead * @see Information\Value::isBlank() * * @return array|bool */ public static function isBlank($value = null) { return Information\Value::isBlank($value); } /** * IS_ERR. * * @param mixed $value Value to check * * @deprecated 1.23.0 Use the isErr() method in the Information\ErrorValue class instead * @see Information\ErrorValue::isErr() * * @return array|bool */ public static function isErr($value = '') { return Information\ErrorValue::isErr($value); } /** * IS_ERROR. * * @param mixed $value Value to check * * @deprecated 1.23.0 Use the isError() method in the Information\ErrorValue class instead * @see Information\ErrorValue::isError() * * @return array|bool */ public static function isError($value = '') { return Information\ErrorValue::isError($value); } /** * IS_NA. * * @param mixed $value Value to check * * @deprecated 1.23.0 Use the isNa() method in the Information\ErrorValue class instead * @see Information\ErrorValue::isNa() * * @return array|bool */ public static function isNa($value = '') { return Information\ErrorValue::isNa($value); } /** * IS_EVEN. * * @param mixed $value Value to check * * @deprecated 1.23.0 Use the isEven() method in the Information\Value class instead * @see Information\Value::isEven() * * @return array|bool|string */ public static function isEven($value = null) { return Information\Value::isEven($value); } /** * IS_ODD. * * @param mixed $value Value to check * * @deprecated 1.23.0 Use the isOdd() method in the Information\Value class instead * @see Information\Value::isOdd() * * @return array|bool|string */ public static function isOdd($value = null) { return Information\Value::isOdd($value); } /** * IS_NUMBER. * * @param mixed $value Value to check * * @deprecated 1.23.0 Use the isNumber() method in the Information\Value class instead * @see Information\Value::isNumber() * * @return array|bool */ public static function isNumber($value = null) { return Information\Value::isNumber($value); } /** * IS_LOGICAL. * * @param mixed $value Value to check * * @deprecated 1.23.0 Use the isLogical() method in the Information\Value class instead * @see Information\Value::isLogical() * * @return array|bool */ public static function isLogical($value = null) { return Information\Value::isLogical($value); } /** * IS_TEXT. * * @param mixed $value Value to check * * @deprecated 1.23.0 Use the isText() method in the Information\Value class instead * @see Information\Value::isText() * * @return array|bool */ public static function isText($value = null) { return Information\Value::isText($value); } /** * IS_NONTEXT. * * @param mixed $value Value to check * * @deprecated 1.23.0 Use the isNonText() method in the Information\Value class instead * @see Information\Value::isNonText() * * @return array|bool */ public static function isNonText($value = null) { return Information\Value::isNonText($value); } /** * N. * * Returns a value converted to a number * * @deprecated 1.23.0 Use the asNumber() method in the Information\Value class instead * @see Information\Value::asNumber() * * @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 * A date The serial number of that date * TRUE 1 * FALSE 0 * An error value The error value * Anything else 0 */ public static function n($value = null) { return Information\Value::asNumber($value); } /** * TYPE. * * Returns a number that identifies the type of a value * * @deprecated 1.23.0 Use the type() method in the Information\Value class instead * @see Information\Value::type() * * @param null|mixed $value The value you want tested * * @return number 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) { return Information\Value::type($value); } /** * Convert a multi-dimensional array to a simple 1-dimensional array. * * @param array|mixed $array Array to be flattened * * @return array Flattened array */ public static function flattenArray($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; } /** * @param mixed $value * * @return null|mixed */ public static function scalar($value) { 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) { 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 * * @return mixed */ public static function flattenSingleValue($value = '') { while (is_array($value)) { $value = array_shift($value); } return $value; } /** * ISFORMULA. * * @deprecated 1.23.0 Use the isFormula() method in the Information\Value class instead * @see Information\Value::isFormula() * * @param mixed $cellReference The cell to check * @param ?Cell $cell The current cell (containing this formula) * * @return array|bool|string */ public static function isFormula($cellReference = '', ?Cell $cell = null) { return Information\Value::isFormula($cellReference, $cell); } 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 (strpos($coordinate, '!') !== false) { $coordinate = substr($coordinate, strrpos($coordinate, '!') + 1); } return $coordinate; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php000064400000054142152427537250021044 0ustar00<'; const OPERATORS_POSTFIX = '%'; /** * Formula. * * @var string */ private $formula; /** * Tokens. * * @var FormulaToken[] */ private $tokens = []; /** * Create a new FormulaParser. * * @param ?string $formula Formula to parse */ public function __construct($formula = '') { // Check parameters if ($formula === null) { throw new Exception('Invalid parameter passed: formula'); } // Initialise values $this->formula = trim($formula); // Parse! $this->parseToTokens(); } /** * Get Formula. * * @return string */ public function getFormula() { 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. * * @return int */ public function getTokenCount() { return count($this->tokens); } /** * Get Tokens. * * @return FormulaToken[] */ public function getTokens() { 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 (strpos(self::OPERATORS_SN, $this->formula[$index]) !== false) { 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 (strlen($value) > 0) { // unexpected $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN); $value = ''; } $inString = true; ++$index; continue; } if ($this->formula[$index] == self::QUOTE_SINGLE) { if (strlen($value) > 0) { // 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 (strlen($value) > 0) { // 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 (strlen($value) > 0) { // 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 (strlen($value) > 0) { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } $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 (strlen($value) > 0) { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } $tmp = array_pop($stack); $tmp->setValue(''); $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP); $tokens1[] = $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 (strlen($value) > 0) { $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 (strlen($value) > 0) { $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 (strpos(self::OPERATORS_INFIX, $this->formula[$index]) !== false) { if (strlen($value) > 0) { $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 (strpos(self::OPERATORS_POSTFIX, $this->formula[$index]) !== false) { if (strlen($value) > 0) { $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 (strlen($value) > 0) { $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 (strlen($value) > 0) { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } $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 (strlen($value) > 0) { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } $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 (strlen($value) > 0) { $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 === null) { continue; } 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 (isset($tokens2[$i + 1])) { // $nextToken = $tokens2[$i + 1]; //} else { // $nextToken = null; //} if ($token === null) { continue; } 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 (strpos('<>=', substr($token->getValue(), 0, 1)) !== false) { $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 (strlen($token->getValue()) > 0) { if (substr($token->getValue(), 0, 1) == '@') { $token->setValue(substr($token->getValue(), 1)); } } } $this->tokens[] = $token; } } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php000064400000746174152427537250020535 0ustar00=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?\$?\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 = '"'; /** @var string */ private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE; /** * Instance of this class. * * @var ?Calculation */ private static $instance; /** * Instance of the spreadsheet this Calculation Engine is using. * * @var ?Spreadsheet */ private $spreadsheet; /** * Calculation cache. * * @var array */ private $calculationCache = []; /** * Calculation cache enabled. * * @var bool */ private $calculationCacheEnabled = true; /** * @var BranchPruner */ private $branchPruner; /** * @var bool */ private $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. * * @var Logger */ private $debugLog; /** * Flag to determine how formula errors should be handled * If true, then a user error will be triggered * If false, then an exception will be thrown. * * @var ?bool * * @deprecated 1.25.2 use setSuppressFormulaErrors() instead */ public $suppressFormulaErrors; /** @var bool */ private $suppressFormulaErrorsNew = false; /** * Error message for any error that was raised/thrown by the calculation engine. * * @var null|string */ public $formulaError; /** * Reference Helper. * * @var ReferenceHelper */ private static $referenceHelper; /** * An array of the nested cell references accessed by the calculation engine, used for the debug log. * * @var CyclicReferenceStack */ private $cyclicReferenceStack; /** @var array */ private $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. * * @var int */ private $cyclicFormulaCounter = 1; /** @var string */ private $cyclicFormulaCell = ''; /** * Number of iterations for cyclic formulae. * * @var int */ public $cyclicFormulaCount = 1; /** * The current locale setting. * * @var string */ private static $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 $validLocaleLanguages = [ 'en', // English (default language) ]; /** * Locale-specific argument separator for function arguments. * * @var string */ private static $localeArgumentSeparator = ','; /** @var array */ private static $localeFunctions = []; /** * Locale-specific translations for Excel constants (True, False and Null). * * @var array */ private static $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 */ private static $excelConstants = [ 'TRUE' => true, 'FALSE' => false, 'NULL' => null, ]; public static function keyInExcelConstants(string $key): bool { return array_key_exists($key, self::$excelConstants); } /** @return mixed */ public static function getExcelConstants(string $key) { 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. * *@var array */ private static $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' => [Information\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' => '1-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' => [Information\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. * * @var array */ private static $controlFunctions = [ 'MKMATRIX' => [ 'argumentCount' => '*', 'functionCall' => [Internal\MakeMatrix::class, 'make'], ], 'NAME.ERROR' => [ 'argumentCount' => '*', 'functionCall' => [Functions::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 (!isset(self::$instance) || (self::$instance === null)) { 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. * * @return Logger */ public function getDebugLog() { 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($returnType) { 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() { return self::$returnArrayAsType; } /** * Is calculation caching enabled? * * @return bool */ public function getCalculationCacheEnabled() { return $this->calculationCacheEnabled; } /** * Enable/disable calculation cache. * * @param bool $calculationCacheEnabled */ public function setCalculationCacheEnabled($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. * * @param string $worksheetName */ public function clearCalculationCacheForWorksheet($worksheetName): void { if (isset($this->calculationCache[$worksheetName])) { unset($this->calculationCache[$worksheetName]); } } /** * Rename calculation cache for a specified worksheet. * * @param string $fromWorksheetName * @param string $toWorksheetName */ public function renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName): void { if (isset($this->calculationCache[$fromWorksheetName])) { $this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName]; unset($this->calculationCache[$fromWorksheetName]); } } /** * Enable/disable calculation cache. * * @param mixed $enabled */ public function setBranchPruningEnabled($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. * * @return string */ public function getLocale() { 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' * * @return bool */ public function setLocale(string $locale) { // Identify our locale and language $language = $locale = strtolower($locale); if (strpos($locale, '_') !== false) { [$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 (strpos($localeFunction, '=') !== false) { [$fName, $lfName] = array_map('trim', explode('=', $localeFunction)); if ((substr($fName, 0, 1) === '*' || 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 $e) { return false; } $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; foreach ($localeSettings as $localeSetting) { [$localeSetting] = explode('##', $localeSetting); // Strip out comments if (strpos($localeSetting, '=') !== false) { [$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 (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) { // So instead we skip replacing in any quoted strings by only replacing in every other array element // after we've exploded the formula $temp = explode(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 $functionReplaceFromExcel; /** @var ?array */ private static $functionReplaceToLocale; /** * @param string $formula * * @return string */ public function _translateFormulaToLocale($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 $functionReplaceFromLocale; /** @var ?array */ private static $functionReplaceToExcel; /** * @param string $formula * * @return string */ public function _translateFormulaToEnglish($formula) { 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, ','); } /** * @param string $function * * @return string */ public static function localeFunc($function) { 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. * * @param mixed $value * * @return mixed */ public static function wrapResult($value) { 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 Information\ExcelError::NAN(); } return $value; } /** * Remove quotes used as a wrapper to identify string values. * * @param mixed $value * * @return mixed */ public static function unwrapResult($value) { 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 Information\ExcelError::NAN(); } return $value; } /** * Calculate cell value (using formula from a cell ID) * Retained for backward compatibility. * * @param Cell $cell Cell to calculate * * @return mixed */ public function calculate(?Cell $cell = null) { 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 * * @return mixed */ public function calculateCellValue(?Cell $cell = null, $resetLog = true) { 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 Information\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 Information\ExcelError::VALUE(); } if (is_array($result[$r])) { $c = array_keys($result[$r]); $c = array_shift($c); if (!is_numeric($c)) { return Information\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 Information\ExcelError::NAN(); } return $result; } /** * Validate and parse a formula string. * * @param string $formula Formula to parse * * @return array|bool */ public function parseFormula($formula) { // 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 * * @return mixed */ public function calculateFormula($formula, $cellID = null, ?Cell $cell = null) { // 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; } /** * @param mixed $cellValue */ public function getValueFromCache(string $cellReference, &$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; } /** * @param string $cellReference * @param mixed $cellValue */ public function saveValueToCache($cellReference, $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 * * @return mixed */ public function _calculateFormulaValue($formula, $cellID = null, ?Cell $cell = null, bool $ignoreQuotePrefix = false) { $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 * * @return array */ private static function checkMatrixOperands(&$operand1, &$operand2, $resize = 1) { // 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); } 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) { $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 mixed $matrix1 First matrix operand * @param mixed $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(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $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 mixed $matrix1 First matrix operand * @param mixed $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(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $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 * * @return mixed */ private function showValue($value) { 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 * * @return null|string */ private function showTypeDetails($value) { 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; } /** * @param string $formula * * @return false|string False indicates an error */ private function convertMatrixReferences($formula) { 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 (strpos($formula, self::FORMULA_OPEN_MATRIX_BRACE) !== false) { // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) { // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded // the formula $temp = explode(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. * * @var array */ private static $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. * * @var array */ private static $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. * * @var array */ private static $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 /** * @param string $formula * * @return array|false */ private function internalParseFormula($formula, ?Cell $cell = null) { 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 = '/^((?' . self::CALCULATION_REGEXP_STRING . ')|(?' . self::CALCULATION_REGEXP_FUNCTION . ')|(?' . self::CALCULATION_REGEXP_CELLREF . ')|(?' . self::CALCULATION_REGEXP_COLUMN_RANGE . ')|(?' . self::CALCULATION_REGEXP_ROW_RANGE . ')|(?' . self::CALCULATION_REGEXP_NUMBER . ')|(?' . self::CALCULATION_REGEXP_OPENBRACE . ')|(?' . self::CALCULATION_REGEXP_STRUCTURED_REFERENCE . ')|(?' . self::CALCULATION_REGEXP_DEFINEDNAME . ')|(?' . 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']; // Scrutinizer says functionCall is unused after this assignment. // It might be right, but I'm too lazy to confirm. $functionCall = self::$controlFunctions[$functionName]['functionCall']; self::doNothing($functionCall); } elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) { $expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount']; $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall']; self::doNothing($functionCall); } 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 != '*') { $isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch); self::doNothing($isOperandOrFunction); 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[] = ['type' => 'Empty Argument', 'value' => self::$excelConstants['NULL'], 'reference' => '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 = Information\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 (strpos($val, '!') === false && $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 (strpos($address, ':') !== false) { // 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 === Information\ExcelError::REF()) { $stackItemReference = $val; } else { $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 ((strpos((string) $val, '.') !== false) || (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[] = ['type' => 'Empty Argument', 'value' => self::$excelConstants['NULL'], 'reference' => '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; } /** * @param array $operandData * * @return mixed */ private static function dataTestReference(&$operandData) { $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; } /** * @param mixed $tokens * @param null|string $cellID * * @return array|false */ private function processTokenStack($tokens, $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 (strpos($cellRange, ':') !== false) { $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', Information\ExcelError::REF(), null); $this->debugLog->writeDebugLog('Evaluated Structured Reference %s as error value %s', $token->value(), Information\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 if (($operand2Data = $stack->pop()) === null) { return $this->raiseFormulaError('Internal error - Operand value missing from stack'); } if (($operand1Data = $stack->pop()) === 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 (strpos($operand1Data['reference'] ?? '', '!') !== false) { [$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true); } else { $sheet1 = ($pCellWorksheet !== null) ? $pCellWorksheet->getTitle() : ''; } [$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 $e) { $stack->push('Error', Information\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', Information\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', Information\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 = Information\ExcelError::REF(); } else { $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10]; if ($matches[2] > '') { $matches[2] = trim($matches[2], "\"'"); if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) { // 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 = Information\ExcelError::REF(); } else { $cellRef = $matches[6] . $matches[7]; if ($matches[2] > '') { $matches[2] = trim($matches[2], "\"'"); if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) { // 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 : Information\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 { $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) { $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 ($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; } /** * @param mixed $operand * @param mixed $stack * * @return bool */ private function validateBinaryOperand(&$operand, &$stack) { 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; } /** * @param mixed $operand1 * @param mixed $operand2 * @param string $operation * * @return array */ private function executeArrayComparison($operand1, $operand2, $operation, Stack &$stack, bool $recursingArrays) { $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; } /** * @param mixed $operand1 * @param mixed $operand2 * @param string $operation * @param bool $recursingArrays * * @return mixed */ private function executeBinaryComparisonOperation($operand1, $operand2, $operation, Stack &$stack, $recursingArrays = false) { // 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; } /** * @param mixed $operand1 * @param mixed $operand2 * @param string $operation * @param Stack $stack * * @return bool|mixed */ private function executeNumericBinaryOperation($operand1, $operand2, $operation, &$stack) { // 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) && strlen($operand1) > 0) || (is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0)) ) { $result = Information\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] = Information\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', Information\ExcelError::DIV0()); $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails(Information\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) { $this->formulaError = $errorMessage; $this->cyclicReferenceStack->clear(); $suppress = /** @scrutinizer ignore-deprecated */ $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 mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned. */ public function extractCellRange(&$range = 'A1', ?Worksheet $worksheet = null, $resetLog = true) { // Return value $returnValue = []; if ($worksheet !== null) { $worksheetName = $worksheet->getTitle(); if (strpos($range, '!') !== false) { [$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 mixed 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, $resetLog = true) { // Return value $returnValue = []; if ($worksheet !== null) { if (strpos($range, '!') !== false) { [$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 Information\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 * * @return bool */ public function isImplemented($function) { $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. * * @return array */ public function getImplementedFunctionNames() { $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; } /** * @return null|mixed */ private function getArgumentDefaultValue(ReflectionParameter $methodArgument) { $defaultValue = null; if ($methodArgument->isDefaultValueAvailable()) { $defaultValue = $methodArgument->getDefaultValue(); if ($methodArgument->isDefaultValueConstant()) { $constantName = $methodArgument->getDefaultValueConstantName() ?? ''; // read constant value if (strpos($constantName, '::') !== false) { [$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. * * @param bool $passCellReference * @param array|string $functionCall * * @return array */ private function addCellReference(array $args, $passCellReference, $functionCall, ?Cell $cell = null) { 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; } /** * @return mixed|string */ private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksheet $cellWorksheet, Stack $stack) { $definedNameScope = $namedRange->getScope(); if ($definedNameScope !== null && $definedNameScope !== $cellWorksheet) { // The defined name isn't in our current scope, so #REF $result = Information\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); $recursiveCalculationCell = ($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); 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; } /** @param mixed $arg */ private static function doNothing($arg): bool { return (bool) $arg; } /** * @param mixed $operand1 * * @return mixed */ private static function boolToString($operand1) { if (is_bool($operand1)) { $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE']; } elseif ($operand1 === null) { $operand1 = ''; } return $operand1; } /** @param mixed $operand */ private static function isNumericOrBool($operand): bool { return is_numeric($operand) || is_bool($operand); } /** @param mixed $operand */ private static function makeError($operand = ''): string { return Information\ErrorValue::isError($operand) ? $operand : Information\ExcelError::VALUE(); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php000064400000010317152427537250020664 0ustar00value = $value; $this->tokenType = $tokenType; $this->tokenSubType = $tokenSubType; } /** * Get Value. * * @return string */ public function getValue() { return $this->value; } /** * Set Value. * * @param string $value */ public function setValue($value): void { $this->value = $value; } /** * Get Token Type (represented by TOKEN_TYPE_*). * * @return string */ public function getTokenType() { return $this->tokenType; } /** * Set Token Type (represented by TOKEN_TYPE_*). * * @param string $value */ public function setTokenType($value): void { $this->tokenType = $value; } /** * Get Token SubType (represented by TOKEN_SUBTYPE_*). * * @return string */ public function getTokenSubType() { return $this->tokenSubType; } /** * Set Token SubType (represented by TOKEN_SUBTYPE_*). * * @param string $value */ public function setTokenSubType($value): void { $this->tokenSubType = $value; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial.php000064400000170415152427537250020150 0ustar00 '' && $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); } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function evaluateComparison($operand1, $operand2, string $operator, bool $useLowercaseFirstComparison): bool { switch ($operator) { // Equality case '=': return self::equal($operand1, $operand2); // Greater than case '>': return self::greaterThan($operand1, $operand2, $useLowercaseFirstComparison); // Less than case '<': return self::lessThan($operand1, $operand2, $useLowercaseFirstComparison); // Greater than or equal case '>=': return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison); // Less than or equal case '<=': return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison); // Inequality case '<>': return self::notEqual($operand1, $operand2); default: throw new Exception('Unsupported binary comparison operator'); } } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function equal($operand1, $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; } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function greaterThanOrEqual($operand1, $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; } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function lessThanOrEqual($operand1, $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; } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function greaterThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool { return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true; } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function lessThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool { return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true; } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function notEqual($operand1, $operand2): bool { return self::equal($operand1, $operand2) !== true; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php000064400000121367152427537250020005 0ustar00 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. * @param mixed $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. * * @return array|string */ public static function DECTOBIN($x, $places = null) { return Engineering\ConvertDecimal::toBinary($x, $places); } /** * DECTOHEX. * * Return a decimal value as hex. * * Excel Function: * DEC2HEX(x[,places]) * * @deprecated 1.17.0 * Use the toHex() method in the Engineering\ConvertDecimal class instead * @see Engineering\ConvertDecimal::toHex() * * @param mixed $x 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. * @param mixed $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. * * @return array|string */ public static function DECTOHEX($x, $places = null) { return Engineering\ConvertDecimal::toHex($x, $places); } /** * DECTOOCT. * * Return an decimal value as octal. * * Excel Function: * DEC2OCT(x[,places]) * * @deprecated 1.17.0 * Use the toOctal() method in the Engineering\ConvertDecimal class instead * @see Engineering\ConvertDecimal::toOctal() * * @param mixed $x 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. * @param mixed $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. * * @return array|string */ public static function DECTOOCT($x, $places = null) { return Engineering\ConvertDecimal::toOctal($x, $places); } /** * HEXTOBIN. * * Return a hex value as binary. * * Excel Function: * HEX2BIN(x[,places]) * * @deprecated 1.17.0 * Use the toBinary() method in the Engineering\ConvertHex class instead * @see Engineering\ConvertHex::toBinary() * * @param mixed $x the hexadecimal number (as a string) that 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. * @param mixed $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. * * @return array|string */ public static function HEXTOBIN($x, $places = null) { return Engineering\ConvertHex::toBinary($x, $places); } /** * HEXTODEC. * * Return a hex value as decimal. * * Excel Function: * HEX2DEC(x) * * @deprecated 1.17.0 * Use the toDecimal() method in the Engineering\ConvertHex class instead * @see Engineering\ConvertHex::toDecimal() * * @param mixed $x The hexadecimal number (as a string) that 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. * * @return array|string */ public static function HEXTODEC($x) { return Engineering\ConvertHex::toDecimal($x); } /** * HEXTOOCT. * * Return a hex value as octal. * * Excel Function: * HEX2OCT(x[,places]) * * @deprecated 1.17.0 * Use the toOctal() method in the Engineering\ConvertHex class instead * @see Engineering\ConvertHex::toOctal() * * @param mixed $x The hexadecimal number (as a string) that 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. * @param mixed $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. * * @return array|string */ public static function HEXTOOCT($x, $places = null) { return Engineering\ConvertHex::toOctal($x, $places); } /** * OCTTOBIN. * * Return an octal value as binary. * * Excel Function: * OCT2BIN(x[,places]) * * @deprecated 1.17.0 * Use the toBinary() method in the Engineering\ConvertOctal class instead * @see Engineering\ConvertOctal::toBinary() * * @param mixed $x 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. * @param mixed $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. * * @return array|string */ public static function OCTTOBIN($x, $places = null) { return Engineering\ConvertOctal::toBinary($x, $places); } /** * OCTTODEC. * * Return an octal value as decimal. * * Excel Function: * OCT2DEC(x) * * @deprecated 1.17.0 * Use the toDecimal() method in the Engineering\ConvertOctal class instead * @see Engineering\ConvertOctal::toDecimal() * * @param mixed $x 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. * * @return array|string */ public static function OCTTODEC($x) { return Engineering\ConvertOctal::toDecimal($x); } /** * OCTTOHEX. * * Return an octal value as hex. * * Excel Function: * OCT2HEX(x[,places]) * * @deprecated 1.17.0 * Use the toHex() method in the Engineering\ConvertOctal class instead * @see Engineering\ConvertOctal::toHex() * * @param mixed $x 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. * @param mixed $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. * * @return array|string */ public static function OCTTOHEX($x, $places = null) { return Engineering\ConvertOctal::toHex($x, $places); } /** * COMPLEX. * * Converts real and imaginary coefficients into a complex number of the form x +/- yi or x +/- yj. * * Excel Function: * COMPLEX(realNumber,imaginary[,suffix]) * * @deprecated 1.18.0 * Use the COMPLEX() method in the Engineering\Complex class instead * @see Engineering\Complex::COMPLEX() * * @param array|float $realNumber the real coefficient of the complex number * @param array|float $imaginary the imaginary coefficient of the complex number * @param array|string $suffix The suffix for the imaginary component of the complex number. * If omitted, the suffix is assumed to be "i". * * @return array|string */ public static function COMPLEX($realNumber = 0.0, $imaginary = 0.0, $suffix = 'i') { return Engineering\Complex::COMPLEX($realNumber, $imaginary, $suffix); } /** * IMAGINARY. * * Returns the imaginary coefficient of a complex number in x + yi or x + yj text format. * * Excel Function: * IMAGINARY(complexNumber) * * @deprecated 1.18.0 * Use the IMAGINARY() method in the Engineering\Complex class instead * @see Engineering\Complex::IMAGINARY() * * @param string $complexNumber the complex number for which you want the imaginary * coefficient * * @return array|float|string */ public static function IMAGINARY($complexNumber) { return Engineering\Complex::IMAGINARY($complexNumber); } /** * IMREAL. * * Returns the real coefficient of a complex number in x + yi or x + yj text format. * * Excel Function: * IMREAL(complexNumber) * * @deprecated 1.18.0 * Use the IMREAL() method in the Engineering\Complex class instead * @see Engineering\Complex::IMREAL() * * @param string $complexNumber the complex number for which you want the real coefficient * * @return array|float|string */ public static function IMREAL($complexNumber) { return Engineering\Complex::IMREAL($complexNumber); } /** * IMABS. * * Returns the absolute value (modulus) of a complex number in x + yi or x + yj text format. * * Excel Function: * IMABS(complexNumber) * * @deprecated 1.18.0 * Use the IMABS() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMABS() * * @param string $complexNumber the complex number for which you want the absolute value * * @return array|float|string */ public static function IMABS($complexNumber) { return ComplexFunctions::IMABS($complexNumber); } /** * 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) * * @deprecated 1.18.0 * Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMARGUMENT() * * @param array|string $complexNumber the complex number for which you want the argument theta * * @return array|float|string */ public static function IMARGUMENT($complexNumber) { return ComplexFunctions::IMARGUMENT($complexNumber); } /** * IMCONJUGATE. * * Returns the complex conjugate of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCONJUGATE(complexNumber) * * @deprecated 1.18.0 * Use the IMCONJUGATE() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMCONJUGATE() * * @param array|string $complexNumber the complex number for which you want the conjugate * * @return array|string */ public static function IMCONJUGATE($complexNumber) { return ComplexFunctions::IMCONJUGATE($complexNumber); } /** * IMCOS. * * Returns the cosine of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCOS(complexNumber) * * @deprecated 1.18.0 * Use the IMCOS() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMCOS() * * @param array|string $complexNumber the complex number for which you want the cosine * * @return array|float|string */ public static function IMCOS($complexNumber) { return ComplexFunctions::IMCOS($complexNumber); } /** * IMCOSH. * * Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCOSH(complexNumber) * * @deprecated 1.18.0 * Use the IMCOSH() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMCOSH() * * @param array|string $complexNumber the complex number for which you want the hyperbolic cosine * * @return array|float|string */ public static function IMCOSH($complexNumber) { return ComplexFunctions::IMCOSH($complexNumber); } /** * IMCOT. * * Returns the cotangent of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCOT(complexNumber) * * @deprecated 1.18.0 * Use the IMCOT() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMCOT() * * @param array|string $complexNumber the complex number for which you want the cotangent * * @return array|float|string */ public static function IMCOT($complexNumber) { return ComplexFunctions::IMCOT($complexNumber); } /** * IMCSC. * * Returns the cosecant of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCSC(complexNumber) * * @deprecated 1.18.0 * Use the IMCSC() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMCSC() * * @param array|string $complexNumber the complex number for which you want the cosecant * * @return array|float|string */ public static function IMCSC($complexNumber) { return ComplexFunctions::IMCSC($complexNumber); } /** * IMCSCH. * * Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCSCH(complexNumber) * * @deprecated 1.18.0 * Use the IMCSCH() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMCSCH() * * @param array|string $complexNumber the complex number for which you want the hyperbolic cosecant * * @return array|float|string */ public static function IMCSCH($complexNumber) { return ComplexFunctions::IMCSCH($complexNumber); } /** * IMSIN. * * Returns the sine of a complex number in x + yi or x + yj text format. * * Excel Function: * IMSIN(complexNumber) * * @deprecated 1.18.0 * Use the IMSIN() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMSIN() * * @param string $complexNumber the complex number for which you want the sine * * @return array|float|string */ public static function IMSIN($complexNumber) { return ComplexFunctions::IMSIN($complexNumber); } /** * IMSINH. * * Returns the hyperbolic sine of a complex number in x + yi or x + yj text format. * * Excel Function: * IMSINH(complexNumber) * * @deprecated 1.18.0 * Use the IMSINH() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMSINH() * * @param string $complexNumber the complex number for which you want the hyperbolic sine * * @return array|float|string */ public static function IMSINH($complexNumber) { return ComplexFunctions::IMSINH($complexNumber); } /** * IMSEC. * * Returns the secant of a complex number in x + yi or x + yj text format. * * Excel Function: * IMSEC(complexNumber) * * @deprecated 1.18.0 * Use the IMSEC() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMSEC() * * @param string $complexNumber the complex number for which you want the secant * * @return array|float|string */ public static function IMSEC($complexNumber) { return ComplexFunctions::IMSEC($complexNumber); } /** * IMSECH. * * Returns the hyperbolic secant of a complex number in x + yi or x + yj text format. * * Excel Function: * IMSECH(complexNumber) * * @deprecated 1.18.0 * Use the IMSECH() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMSECH() * * @param string $complexNumber the complex number for which you want the hyperbolic secant * * @return array|float|string */ public static function IMSECH($complexNumber) { return ComplexFunctions::IMSECH($complexNumber); } /** * IMTAN. * * Returns the tangent of a complex number in x + yi or x + yj text format. * * Excel Function: * IMTAN(complexNumber) * * @deprecated 1.18.0 * Use the IMTAN() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMTAN() * * @param string $complexNumber the complex number for which you want the tangent * * @return array|float|string */ public static function IMTAN($complexNumber) { return ComplexFunctions::IMTAN($complexNumber); } /** * IMSQRT. * * Returns the square root of a complex number in x + yi or x + yj text format. * * Excel Function: * IMSQRT(complexNumber) * * @deprecated 1.18.0 * Use the IMSQRT() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMSQRT() * * @param string $complexNumber the complex number for which you want the square root * * @return array|string */ public static function IMSQRT($complexNumber) { return ComplexFunctions::IMSQRT($complexNumber); } /** * IMLN. * * Returns the natural logarithm of a complex number in x + yi or x + yj text format. * * Excel Function: * IMLN(complexNumber) * * @deprecated 1.18.0 * Use the IMLN() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMLN() * * @param string $complexNumber the complex number for which you want the natural logarithm * * @return array|string */ public static function IMLN($complexNumber) { return ComplexFunctions::IMLN($complexNumber); } /** * IMLOG10. * * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format. * * Excel Function: * IMLOG10(complexNumber) * * @deprecated 1.18.0 * Use the IMLOG10() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMLOG10() * * @param string $complexNumber the complex number for which you want the common logarithm * * @return array|string */ public static function IMLOG10($complexNumber) { return ComplexFunctions::IMLOG10($complexNumber); } /** * IMLOG2. * * Returns the base-2 logarithm of a complex number in x + yi or x + yj text format. * * Excel Function: * IMLOG2(complexNumber) * * @deprecated 1.18.0 * Use the IMLOG2() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMLOG2() * * @param string $complexNumber the complex number for which you want the base-2 logarithm * * @return array|string */ public static function IMLOG2($complexNumber) { return ComplexFunctions::IMLOG2($complexNumber); } /** * IMEXP. * * Returns the exponential of a complex number in x + yi or x + yj text format. * * Excel Function: * IMEXP(complexNumber) * * @deprecated 1.18.0 * Use the IMEXP() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMEXP() * * @param string $complexNumber the complex number for which you want the exponential * * @return array|string */ public static function IMEXP($complexNumber) { return ComplexFunctions::IMEXP($complexNumber); } /** * IMPOWER. * * Returns a complex number in x + yi or x + yj text format raised to a power. * * Excel Function: * IMPOWER(complexNumber,realNumber) * * @deprecated 1.18.0 * Use the IMPOWER() method in the Engineering\ComplexFunctions class instead * @see ComplexFunctions::IMPOWER() * * @param string $complexNumber the complex number you want to raise to a power * @param float $realNumber the power to which you want to raise the complex number * * @return array|string */ public static function IMPOWER($complexNumber, $realNumber) { return ComplexFunctions::IMPOWER($complexNumber, $realNumber); } /** * IMDIV. * * Returns the quotient of two complex numbers in x + yi or x + yj text format. * * Excel Function: * IMDIV(complexDividend,complexDivisor) * * @deprecated 1.18.0 * Use the IMDIV() method in the Engineering\ComplexOperations class instead * @see ComplexOperations::IMDIV() * * @param string $complexDividend the complex numerator or dividend * @param string $complexDivisor the complex denominator or divisor * * @return array|string */ public static function IMDIV($complexDividend, $complexDivisor) { return ComplexOperations::IMDIV($complexDividend, $complexDivisor); } /** * IMSUB. * * Returns the difference of two complex numbers in x + yi or x + yj text format. * * Excel Function: * IMSUB(complexNumber1,complexNumber2) * * @deprecated 1.18.0 * Use the IMSUB() method in the Engineering\ComplexOperations class instead * @see ComplexOperations::IMSUB() * * @param string $complexNumber1 the complex number from which to subtract complexNumber2 * @param string $complexNumber2 the complex number to subtract from complexNumber1 * * @return array|string */ public static function IMSUB($complexNumber1, $complexNumber2) { return ComplexOperations::IMSUB($complexNumber1, $complexNumber2); } /** * IMSUM. * * Returns the sum of two or more complex numbers in x + yi or x + yj text format. * * Excel Function: * IMSUM(complexNumber[,complexNumber[,...]]) * * @deprecated 1.18.0 * Use the IMSUM() method in the Engineering\ComplexOperations class instead * @see ComplexOperations::IMSUM() * * @param string ...$complexNumbers Series of complex numbers to add * * @return string */ public static function IMSUM(...$complexNumbers) { return ComplexOperations::IMSUM(...$complexNumbers); } /** * IMPRODUCT. * * Returns the product of two or more complex numbers in x + yi or x + yj text format. * * Excel Function: * IMPRODUCT(complexNumber[,complexNumber[,...]]) * * @deprecated 1.18.0 * Use the IMPRODUCT() method in the Engineering\ComplexOperations class instead * @see ComplexOperations::IMPRODUCT() * * @param string ...$complexNumbers Series of complex numbers to multiply * * @return string */ public static function IMPRODUCT(...$complexNumbers) { return ComplexOperations::IMPRODUCT(...$complexNumbers); } /** * DELTA. * * 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. * * Excel Function: * DELTA(a[,b]) * * @deprecated 1.17.0 * Use the DELTA() method in the Engineering\Compare class instead * @see Engineering\Compare::DELTA() * * @param float $a the first number * @param float $b The second number. If omitted, b is assumed to be zero. * * @return array|int|string (string in the event of an error) */ public static function DELTA($a, $b = 0) { return Engineering\Compare::DELTA($a, $b); } /** * 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. * * @deprecated 1.17.0 * Use the GESTEP() method in the Engineering\Compare class instead * @see Engineering\Compare::GESTEP() * * @param float $number the value to test against step * @param float $step The threshold value. If you omit a value for step, GESTEP uses zero. * * @return array|int|string (string in the event of an error) */ public static function GESTEP($number, $step = 0) { return Engineering\Compare::GESTEP($number, $step); } /** * BITAND. * * Returns the bitwise AND of two integer values. * * Excel Function: * BITAND(number1, number2) * * @deprecated 1.17.0 * Use the BITAND() method in the Engineering\BitWise class instead * @see Engineering\BitWise::BITAND() * * @param int $number1 * @param int $number2 * * @return array|int|string */ public static function BITAND($number1, $number2) { return Engineering\BitWise::BITAND($number1, $number2); } /** * BITOR. * * Returns the bitwise OR of two integer values. * * Excel Function: * BITOR(number1, number2) * * @deprecated 1.17.0 * Use the BITOR() method in the Engineering\BitWise class instead * @see Engineering\BitWise::BITOR() * * @param int $number1 * @param int $number2 * * @return array|int|string */ public static function BITOR($number1, $number2) { return Engineering\BitWise::BITOR($number1, $number2); } /** * BITXOR. * * Returns the bitwise XOR of two integer values. * * Excel Function: * BITXOR(number1, number2) * * @deprecated 1.17.0 * Use the BITXOR() method in the Engineering\BitWise class instead * @see Engineering\BitWise::BITXOR() * * @param int $number1 * @param int $number2 * * @return array|int|string */ public static function BITXOR($number1, $number2) { return Engineering\BitWise::BITXOR($number1, $number2); } /** * BITLSHIFT. * * Returns the number value shifted left by shift_amount bits. * * Excel Function: * BITLSHIFT(number, shift_amount) * * @deprecated 1.17.0 * Use the BITLSHIFT() method in the Engineering\BitWise class instead * @see Engineering\BitWise::BITLSHIFT() * * @param int $number * @param int $shiftAmount * * @return array|float|int|string */ public static function BITLSHIFT($number, $shiftAmount) { return Engineering\BitWise::BITLSHIFT($number, $shiftAmount); } /** * BITRSHIFT. * * Returns the number value shifted right by shift_amount bits. * * Excel Function: * BITRSHIFT(number, shift_amount) * * @deprecated 1.17.0 * Use the BITRSHIFT() method in the Engineering\BitWise class instead * @see Engineering\BitWise::BITRSHIFT() * * @param int $number * @param int $shiftAmount * * @return array|float|int|string */ public static function BITRSHIFT($number, $shiftAmount) { return Engineering\BitWise::BITRSHIFT($number, $shiftAmount); } /** * 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]) * * @deprecated 1.17.0 * Use the ERF() method in the Engineering\Erf class instead * @see Engineering\Erf::ERF() * * @param float $lower lower bound for integrating ERF * @param float $upper upper bound for integrating ERF. * If omitted, ERF integrates between zero and lower_limit * * @return array|float|string */ public static function ERF($lower, $upper = null) { return Engineering\Erf::ERF($lower, $upper); } /** * ERFPRECISE. * * Returns the error function integrated between the lower and upper bound arguments. * * Excel Function: * ERF.PRECISE(limit) * * @deprecated 1.17.0 * Use the ERFPRECISE() method in the Engineering\Erf class instead * @see Engineering\Erf::ERFPRECISE() * * @param float $limit bound for integrating ERF * * @return array|float|string */ public static function ERFPRECISE($limit) { return Engineering\Erf::ERFPRECISE($limit); } /** * 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) * * @deprecated 1.17.0 * Use the ERFC() method in the Engineering\ErfC class instead * @see Engineering\ErfC::ERFC() * * @param float $x The lower bound for integrating ERFC * * @return array|float|string */ public static function ERFC($x) { return Engineering\ErfC::ERFC($x); } /** * getConversionGroups * Returns a list of the different conversion groups for UOM conversions. * * @deprecated 1.16.0 * Use the getConversionCategories() method in the Engineering\ConvertUOM class instead * @see Engineering\ConvertUOM::getConversionCategories() * * @return array */ public static function getConversionGroups() { return Engineering\ConvertUOM::getConversionCategories(); } /** * getConversionGroupUnits * Returns an array of units of measure, for a specified conversion group, or for all groups. * * @deprecated 1.16.0 * Use the getConversionCategoryUnits() method in the ConvertUOM class instead * @see Engineering\ConvertUOM::getConversionCategoryUnits() * * @param null|mixed $category * * @return array */ public static function getConversionGroupUnits($category = null) { return Engineering\ConvertUOM::getConversionCategoryUnits($category); } /** * getConversionGroupUnitDetails. * * @deprecated 1.16.0 * Use the getConversionCategoryUnitDetails() method in the ConvertUOM class instead * @see Engineering\ConvertUOM::getConversionCategoryUnitDetails() * * @param null|mixed $category * * @return array */ public static function getConversionGroupUnitDetails($category = null) { return Engineering\ConvertUOM::getConversionCategoryUnitDetails($category); } /** * getConversionMultipliers * Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). * * @deprecated 1.16.0 * Use the getConversionMultipliers() method in the ConvertUOM class instead * @see Engineering\ConvertUOM::getConversionMultipliers() * * @return mixed[] */ public static function getConversionMultipliers() { return Engineering\ConvertUOM::getConversionMultipliers(); } /** * getBinaryConversionMultipliers. * * Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure * in CONVERTUOM(). * * @deprecated 1.16.0 * Use the getBinaryConversionMultipliers() method in the ConvertUOM class instead * @see Engineering\ConvertUOM::getBinaryConversionMultipliers() * * @return mixed[] */ public static function getBinaryConversionMultipliers() { return Engineering\ConvertUOM::getBinaryConversionMultipliers(); } /** * CONVERTUOM. * * 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) * * @deprecated 1.16.0 * Use the CONVERT() method in the ConvertUOM class instead * @see Engineering\ConvertUOM::CONVERT() * * @param float|int $value the value in fromUOM to convert * @param string $fromUOM the units for value * @param string $toUOM the units for the result * * @return array|float|string */ public static function CONVERTUOM($value, $fromUOM, $toUOM) { return Engineering\ConvertUOM::CONVERT($value, $fromUOM, $toUOM); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php000064400000106247152427537250017762 0ustar00getMessage(); } } /** * 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() * * @deprecated 1.18.0 * Use the now method in the DateTimeExcel\Current class instead * @see DateTimeExcel\Current::now() * * @return mixed 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 DATETIMENOW() { return DateTimeExcel\Current::now(); } /** * 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() * * @deprecated 1.18.0 * Use the today method in the DateTimeExcel\Current class instead * @see DateTimeExcel\Current::today() * * @return mixed 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 DATENOW() { return DateTimeExcel\Current::today(); } /** * 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) * * @deprecated 1.18.0 * Use the fromYMD method in the DateTimeExcel\Date class instead * @see DateTimeExcel\Date::fromYMD() * * 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 int $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 int $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 int $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 mixed 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 DATE($year = 0, $month = 1, $day = 1) { return DateTimeExcel\Date::fromYMD($year, $month, $day); } /** * 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) * * @deprecated 1.18.0 * Use the fromHMS method in the DateTimeExcel\Time class instead * @see DateTimeExcel\Time::fromHMS() * * @param int $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 int $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 int $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 * * @return mixed 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 TIME($hour = 0, $minute = 0, $second = 0) { return DateTimeExcel\Time::fromHMS($hour, $minute, $second); } /** * 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) * * @deprecated 1.18.0 * Use the fromString method in the DateTimeExcel\DateValue class instead * @see DateTimeExcel\DateValue::fromString() * * @param 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. * * @return mixed 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 DATEVALUE($dateValue) { return DateTimeExcel\DateValue::fromString($dateValue); } /** * 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) * * @deprecated 1.18.0 * Use the fromString method in the DateTimeExcel\TimeValue class instead * @see DateTimeExcel\TimeValue::fromString() * * @param 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. * * @return mixed 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 TIMEVALUE($timeValue) { return DateTimeExcel\TimeValue::fromString($timeValue); } /** * DATEDIF. * * Excel Function: * DATEDIF(startdate, enddate, unit) * * @deprecated 1.18.0 * Use the interval method in the DateTimeExcel\Difference class instead * @see DateTimeExcel\Difference::interval() * * @param mixed $startDate Excel date serial value, PHP date/time stamp, PHP DateTime object * or a standard date string * @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object * or a standard date string * @param array|string $unit * * @return array|int|string Interval between the dates */ public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D') { return DateTimeExcel\Difference::interval($startDate, $endDate, $unit); } /** * DAYS. * * Returns the number of days between two dates * * Excel Function: * DAYS(endDate, startDate) * * @deprecated 1.18.0 * Use the between method in the DateTimeExcel\Days class instead * @see DateTimeExcel\Days::between() * * @param array|DateTimeInterface|float|int|string $endDate Excel date serial value (float), * PHP date timestamp (integer), PHP DateTime object, or a standard date string * @param array|DateTimeInterface|float|int|string $startDate Excel date serial value (float), * PHP date timestamp (integer), PHP DateTime object, or a standard date string * * @return array|int|string Number of days between start date and end date or an error */ public static function DAYS($endDate = 0, $startDate = 0) { return DateTimeExcel\Days::between($endDate, $startDate); } /** * 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]) * * @deprecated 1.18.0 * Use the between method in the DateTimeExcel\Days360 class instead * @see DateTimeExcel\Days360::between() * * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param array|bool $method US or European Method * 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. * * @return array|int|string Number of days between start date and end date */ public static function DAYS360($startDate = 0, $endDate = 0, $method = false) { return DateTimeExcel\Days360::between($startDate, $endDate, $method); } /** * 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]) * * @deprecated 1.18.0 * Use the fraction method in the DateTimeExcel\YearFrac class instead * @see DateTimeExcel\YearFrac::fraction() * * 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 * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @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 * * @return array|float|string fraction of the year, or a string containing an error */ public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0) { return DateTimeExcel\YearFrac::fraction($startDate, $endDate, $method); } /** * 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[,...]]]) * * @deprecated 1.18.0 * Use the count method in the DateTimeExcel\NetworkDays class instead * @see DateTimeExcel\NetworkDays::count() * * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param mixed $dateArgs * * @return array|int|string Interval between the dates */ public static function NETWORKDAYS($startDate, $endDate, ...$dateArgs) { return DateTimeExcel\NetworkDays::count($startDate, $endDate, ...$dateArgs); } /** * 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[,...]]]) * * @deprecated 1.18.0 * Use the date method in the DateTimeExcel\WorkDay class instead * @see DateTimeExcel\WorkDay::date() * * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param 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. * @param mixed $dateArgs * * @return mixed 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 WORKDAY($startDate, $endDays, ...$dateArgs) { return DateTimeExcel\WorkDay::date($startDate, $endDays, ...$dateArgs); } /** * 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) * * @deprecated 1.18.0 * Use the day method in the DateTimeExcel\DateParts class instead * @see DateTimeExcel\DateParts::day() * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * * @return array|int|string Day of the month */ public static function DAYOFMONTH($dateValue = 1) { return DateTimeExcel\DateParts::day($dateValue); } /** * 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]) * * @deprecated 1.18.0 * Use the day method in the DateTimeExcel\Week class instead * @see DateTimeExcel\Week::day() * * @param float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param int $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). * * @return array|int|string Day of the week value */ public static function WEEKDAY($dateValue = 1, $style = 1) { return DateTimeExcel\Week::day($dateValue, $style); } /** * STARTWEEK_SUNDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::STARTWEEK_SUNDAY * @see DateTimeExcel\Constants::STARTWEEK_SUNDAY */ const STARTWEEK_SUNDAY = 1; /** * STARTWEEK_MONDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::STARTWEEK_MONDAY * @see DateTimeExcel\Constants::STARTWEEK_MONDAY */ const STARTWEEK_MONDAY = 2; /** * STARTWEEK_MONDAY_ALT. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::STARTWEEK_MONDAY_ALT * @see DateTimeExcel\Constants::STARTWEEK_MONDAY_ALT */ const STARTWEEK_MONDAY_ALT = 11; /** * STARTWEEK_TUESDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::STARTWEEK_TUESDAY * @see DateTimeExcel\Constants::STARTWEEK_TUESDAY */ const STARTWEEK_TUESDAY = 12; /** * STARTWEEK_WEDNESDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::STARTWEEK_WEDNESDAY * @see DateTimeExcel\Constants::STARTWEEK_WEDNESDAY */ const STARTWEEK_WEDNESDAY = 13; /** * STARTWEEK_THURSDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::STARTWEEK_THURSDAY * @see DateTimeExcel\Constants::STARTWEEK_THURSDAY */ const STARTWEEK_THURSDAY = 14; /** * STARTWEEK_FRIDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::STARTWEEK_FRIDAY * @see DateTimeExcel\Constants::STARTWEEK_FRIDAY */ const STARTWEEK_FRIDAY = 15; /** * STARTWEEK_SATURDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::STARTWEEK_SATURDAY * @see DateTimeExcel\Constants::STARTWEEK_SATURDAY */ const STARTWEEK_SATURDAY = 16; /** * STARTWEEK_SUNDAY_ALT. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::STARTWEEK_SUNDAY_ALT * @see DateTimeExcel\Constants::STARTWEEK_SUNDAY_ALT */ const STARTWEEK_SUNDAY_ALT = 17; /** * DOW_SUNDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::DOW_SUNDAY * @see DateTimeExcel\Constants::DOW_SUNDAY */ const DOW_SUNDAY = 1; /** * DOW_MONDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::DOW_MONDAY * @see DateTimeExcel\Constants::DOW_MONDAY */ const DOW_MONDAY = 2; /** * DOW_TUESDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::DOW_TUESDAY * @see DateTimeExcel\Constants::DOW_TUESDAY */ const DOW_TUESDAY = 3; /** * DOW_WEDNESDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::DOW_WEDNESDAY * @see DateTimeExcel\Constants::DOW_WEDNESDAY */ const DOW_WEDNESDAY = 4; /** * DOW_THURSDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::DOW_THURSDAY * @see DateTimeExcel\Constants::DOW_THURSDAY */ const DOW_THURSDAY = 5; /** * DOW_FRIDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::DOW_FRIDAY * @see DateTimeExcel\Constants::DOW_FRIDAY */ const DOW_FRIDAY = 6; /** * DOW_SATURDAY. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::DOW_SATURDAY * @see DateTimeExcel\Constants::DOW_SATURDAY */ const DOW_SATURDAY = 7; /** * STARTWEEK_MONDAY_ISO. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::STARTWEEK_MONDAY_ISO * @see DateTimeExcel\Constants::STARTWEEK_MONDAY_ISO */ const STARTWEEK_MONDAY_ISO = 21; /** * METHODARR. * * @deprecated 1.18.0 * Use DateTimeExcel\Constants::METHODARR * @see DateTimeExcel\Constants::METHODARR */ 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, ]; /** * 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]) * * @deprecated 1.18.0 * Use the number method in the DateTimeExcel\Week class instead * @see DateTimeExcel\Week::number() * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param 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). * * @return array|int|string Week Number */ public static function WEEKNUM($dateValue = 1, $method = /** @scrutinizer ignore-deprecated */ self::STARTWEEK_SUNDAY) { return DateTimeExcel\Week::number($dateValue, $method); } /** * ISOWEEKNUM. * * Returns the ISO 8601 week number of the year for a specified date. * * Excel Function: * ISOWEEKNUM(dateValue) * * @deprecated 1.18.0 * Use the isoWeekNumber method in the DateTimeExcel\Week class instead * @see DateTimeExcel\Week::isoWeekNumber() * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * * @return array|int|string Week Number */ public static function ISOWEEKNUM($dateValue = 1) { return DateTimeExcel\Week::isoWeekNumber($dateValue); } /** * 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) * * @deprecated 1.18.0 * Use the month method in the DateTimeExcel\DateParts class instead * @see DateTimeExcel\DateParts::month() * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * * @return array|int|string Month of the year */ public static function MONTHOFYEAR($dateValue = 1) { return DateTimeExcel\DateParts::month($dateValue); } /** * YEAR. * * Returns the year corresponding to a date. * The year is returned as an integer in the range 1900-9999. * * Excel Function: * YEAR(dateValue) * * @deprecated 1.18.0 * Use the ear method in the DateTimeExcel\DateParts class instead * @see DateTimeExcel\DateParts::year() * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * * @return array|int|string Year */ public static function YEAR($dateValue = 1) { return DateTimeExcel\DateParts::year($dateValue); } /** * 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) * * @deprecated 1.18.0 * Use the hour method in the DateTimeExcel\TimeParts class instead * @see DateTimeExcel\TimeParts::hour() * * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard time string * * @return array|int|string Hour */ public static function HOUROFDAY($timeValue = 0) { return DateTimeExcel\TimeParts::hour($timeValue); } /** * MINUTE. * * Returns the minutes of a time value. * The minute is given as an integer, ranging from 0 to 59. * * Excel Function: * MINUTE(timeValue) * * @deprecated 1.18.0 * Use the minute method in the DateTimeExcel\TimeParts class instead * @see DateTimeExcel\TimeParts::minute() * * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard time string * * @return array|int|string Minute */ public static function MINUTE($timeValue = 0) { return DateTimeExcel\TimeParts::minute($timeValue); } /** * SECOND. * * Returns the seconds of a time value. * The second is given as an integer in the range 0 (zero) to 59. * * Excel Function: * SECOND(timeValue) * * @deprecated 1.18.0 * Use the second method in the DateTimeExcel\TimeParts class instead * @see DateTimeExcel\TimeParts::second() * * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard time string * * @return array|int|string Second */ public static function SECOND($timeValue = 0) { return DateTimeExcel\TimeParts::second($timeValue); } /** * 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) * * @deprecated 1.18.0 * Use the adjust method in the DateTimeExcel\Edate class instead * @see DateTimeExcel\Month::adjust() * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param 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. * * @return mixed 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 EDATE($dateValue = 1, $adjustmentMonths = 0) { return DateTimeExcel\Month::adjust($dateValue, $adjustmentMonths); } /** * 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) * * @deprecated 1.18.0 * Use the lastDay method in the DateTimeExcel\EoMonth class instead * @see DateTimeExcel\Month::lastDay() * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param 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. * * @return mixed 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 EOMONTH($dateValue = 1, $adjustmentMonths = 0) { return DateTimeExcel\Month::lastDay($dateValue, $adjustmentMonths); } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Database.php000064400000056645152427537250020000 0ustar00line = $line; $e->file = $file; throw $e; } } phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php000064400000001321152427537250020026 0ustar00visible = false; $this->blurRadius = 6; $this->distance = 2; $this->direction = 0; $this->alignment = self::SHADOW_BOTTOM_RIGHT; $this->color = new Color(Color::COLOR_BLACK); $this->alpha = 50; } /** * Get Visible. * * @return bool */ public function getVisible() { return $this->visible; } /** * Set Visible. * * @param bool $visible * * @return $this */ public function setVisible($visible) { $this->visible = $visible; return $this; } /** * Get Blur radius. * * @return int */ public function getBlurRadius() { return $this->blurRadius; } /** * Set Blur radius. * * @param int $blurRadius * * @return $this */ public function setBlurRadius($blurRadius) { $this->blurRadius = $blurRadius; return $this; } /** * Get Shadow distance. * * @return int */ public function getDistance() { return $this->distance; } /** * Set Shadow distance. * * @param int $distance * * @return $this */ public function setDistance($distance) { $this->distance = $distance; return $this; } /** * Get Shadow direction (in degrees). * * @return int */ public function getDirection() { return $this->direction; } /** * Set Shadow direction (in degrees). * * @param int $direction * * @return $this */ public function setDirection($direction) { $this->direction = $direction; return $this; } /** * Get Shadow alignment. * * @return string */ public function getAlignment() { return $this->alignment; } /** * Set Shadow alignment. * * @param string $alignment * * @return $this */ public function setAlignment($alignment) { $this->alignment = $alignment; return $this; } /** * Get Color. * * @return Color */ public function getColor() { return $this->color; } /** * Set Color. * * @return $this */ public function setColor(Color $color) { $this->color = $color; return $this; } /** * Get Alpha. * * @return int */ public function getAlpha() { return $this->alpha; } /** * Set Alpha. * * @param int $alpha * * @return $this */ public function setAlpha($alpha) { $this->alpha = $alpha; return $this; } /** * Get hash code. * * @return string Hash code */ public function getHashCode() { return md5( ($this->visible ? 't' : 'f') . $this->blurRadius . $this->distance . $this->direction . $this->alignment . $this->color->getHashCode() . $this->alpha . __CLASS__ ); } /** * 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) { if (is_object($value)) { $this->$key = clone $value; } else { $this->$key = $value; } } } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php000064400000013771152427537250020266 0ustar00columnIndex = $column; $this->table = $table; } /** * Get Table column index as string eg: 'A'. */ public function getColumnIndex(): string { return $this->columnIndex; } /** * Set Table column index as string eg: 'A'. * * @param string $column Column (e.g. A) */ public function setColumnIndex($column): self { // Uppercase coordinate $column = strtoupper($column); if ($this->table !== null) { $this->table->isColumnInRange($column); } $this->columnIndex = $column; return $this; } /** * Get show Filter Button. */ public function getShowFilterButton(): bool { return $this->showFilterButton; } /** * Set show Filter Button. */ public function setShowFilterButton(bool $showFilterButton): self { $this->showFilterButton = $showFilterButton; return $this; } /** * Get total Row Label. */ public function getTotalsRowLabel(): ?string { return $this->totalsRowLabel; } /** * Set total Row Label. */ public function setTotalsRowLabel(string $totalsRowLabel): self { $this->totalsRowLabel = $totalsRowLabel; return $this; } /** * Get total Row Function. */ public function getTotalsRowFunction(): ?string { return $this->totalsRowFunction; } /** * Set total Row Function. */ public function setTotalsRowFunction(string $totalsRowFunction): self { $this->totalsRowFunction = $totalsRowFunction; return $this; } /** * Get total Row Formula. */ public function getTotalsRowFormula(): ?string { return $this->totalsRowFormula; } /** * Set total Row Formula. */ public function setTotalsRowFormula(string $totalsRowFormula): self { $this->totalsRowFormula = $totalsRowFormula; return $this; } /** * Get column Formula. */ public function getColumnFormula(): ?string { return $this->columnFormula; } /** * Set column Formula. */ public function setColumnFormula(string $columnFormula): self { $this->columnFormula = $columnFormula; return $this; } /** * Get this Column's Table. */ public function getTable(): ?Table { return $this->table; } /** * Set this Column's Table. */ public function setTable(?Table $table = null): self { $this->table = $table; return $this; } public static function updateStructuredReferences(?Worksheet $workSheet, ?string $oldTitle, ?string $newTitle): void { if ($workSheet === null || $oldTitle === null || $oldTitle === '' || $newTitle === null) { return; } // Remember that table headings are case-insensitive if (StringHelper::strToLower($oldTitle) !== StringHelper::strToLower($newTitle)) { // We need to check all formula cells that might contain Structured References that refer // to this column, and update those formulae to reference the new column text $spreadsheet = $workSheet->getParentOrThrow(); foreach ($spreadsheet->getWorksheetIterator() as $sheet) { self::updateStructuredReferencesInCells($sheet, $oldTitle, $newTitle); } self::updateStructuredReferencesInNamedFormulae($spreadsheet, $oldTitle, $newTitle); } } private static function updateStructuredReferencesInCells(Worksheet $worksheet, string $oldTitle, string $newTitle): void { $pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui'; foreach ($worksheet->getCoordinates(false) as $coordinate) { $cell = $worksheet->getCell($coordinate); if ($cell->getDataType() === DataType::TYPE_FORMULA) { $formula = $cell->getValue(); if (preg_match($pattern, $formula) === 1) { $formula = preg_replace($pattern, "[$1{$newTitle}]", $formula); $cell->setValueExplicit($formula, DataType::TYPE_FORMULA); } } } } private static function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $oldTitle, string $newTitle): void { $pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui'; foreach ($spreadsheet->getNamedFormulae() as $namedFormula) { $formula = $namedFormula->getValue(); if (preg_match($pattern, $formula) === 1) { $formula = preg_replace($pattern, "[$1{$newTitle}]", $formula); $namedFormula->setValue($formula); // @phpstan-ignore-line } } } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/TableStyle.php000064400000013712152427537250021074 0ustar00theme = $theme; } /** * Get theme. */ public function getTheme(): string { return $this->theme; } /** * Set theme. */ public function setTheme(string $theme): self { $this->theme = $theme; return $this; } /** * Get show First Column. */ public function getShowFirstColumn(): bool { return $this->showFirstColumn; } /** * Set show First Column. */ public function setShowFirstColumn(bool $showFirstColumn): self { $this->showFirstColumn = $showFirstColumn; return $this; } /** * Get show Last Column. */ public function getShowLastColumn(): bool { return $this->showLastColumn; } /** * Set show Last Column. */ public function setShowLastColumn(bool $showLastColumn): self { $this->showLastColumn = $showLastColumn; return $this; } /** * Get show Row Stripes. */ public function getShowRowStripes(): bool { return $this->showRowStripes; } /** * Set show Row Stripes. */ public function setShowRowStripes(bool $showRowStripes): self { $this->showRowStripes = $showRowStripes; return $this; } /** * Get show Column Stripes. */ public function getShowColumnStripes(): bool { return $this->showColumnStripes; } /** * Set show Column Stripes. */ public function setShowColumnStripes(bool $showColumnStripes): self { $this->showColumnStripes = $showColumnStripes; return $this; } /** * Get this Style's Table. */ public function getTable(): ?Table { return $this->table; } /** * Set this Style's Table. */ public function setTable(?Table $table = null): self { $this->table = $table; return $this; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php000064400000035132152427537250022217 0ustar00parent = $parent; } private function setEvaluatedFalse(): void { if ($this->parent !== null) { $this->parent->setEvaluatedFalse(); } } /** * Get AutoFilter Rule Type. * * @return string */ public function getRuleType() { return $this->ruleType; } /** * Set AutoFilter Rule Type. * * @param string $ruleType see self::AUTOFILTER_RULETYPE_* * * @return $this */ public function setRuleType($ruleType) { $this->setEvaluatedFalse(); if (!in_array($ruleType, self::RULE_TYPES)) { throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.'); } $this->ruleType = $ruleType; return $this; } /** * Get AutoFilter Rule Value. * * @return int|int[]|string|string[] */ public function getValue() { return $this->value; } /** * Set AutoFilter Rule Value. * * @param int|int[]|string|string[] $value * * @return $this */ public function setValue($value) { $this->setEvaluatedFalse(); if (is_array($value)) { $grouping = -1; foreach ($value as $key => $v) { // Validate array entries if (!in_array($key, self::DATE_TIME_GROUPS)) { // Remove any invalid entries from the value array unset($value[$key]); } else { // Work out what the dateTime grouping will be $grouping = max($grouping, array_search($key, self::DATE_TIME_GROUPS)); } } if (count($value) == 0) { throw new PhpSpreadsheetException('Invalid rule value for column AutoFilter Rule.'); } // Set the dateTime grouping that we've anticipated $this->setGrouping(self::DATE_TIME_GROUPS[$grouping]); } $this->value = $value; return $this; } /** * Get AutoFilter Rule Operator. * * @return string */ public function getOperator() { return $this->operator; } /** * Set AutoFilter Rule Operator. * * @param string $operator see self::AUTOFILTER_COLUMN_RULE_* * * @return $this */ public function setOperator($operator) { $this->setEvaluatedFalse(); if (empty($operator)) { $operator = self::AUTOFILTER_COLUMN_RULE_EQUAL; } if ( (!in_array($operator, self::OPERATORS)) && (!in_array($operator, self::TOP_TEN_VALUE)) ) { throw new PhpSpreadsheetException('Invalid operator for column AutoFilter Rule.'); } $this->operator = $operator; return $this; } /** * Get AutoFilter Rule Grouping. * * @return string */ public function getGrouping() { return $this->grouping; } /** * Set AutoFilter Rule Grouping. * * @param string $grouping * * @return $this */ public function setGrouping($grouping) { $this->setEvaluatedFalse(); if ( ($grouping !== null) && (!in_array($grouping, self::DATE_TIME_GROUPS)) && (!in_array($grouping, self::DYNAMIC_TYPES)) && (!in_array($grouping, self::TOP_TEN_TYPE)) ) { throw new PhpSpreadsheetException('Invalid grouping for column AutoFilter Rule.'); } $this->grouping = $grouping; return $this; } /** * Set AutoFilter Rule. * * @param string $operator see self::AUTOFILTER_COLUMN_RULE_* * @param int|int[]|string|string[] $value * @param string $grouping * * @return $this */ public function setRule($operator, $value, $grouping = null) { $this->setEvaluatedFalse(); $this->setOperator($operator); $this->setValue($value); // Only set grouping if it's been passed in as a user-supplied argument, // otherwise we're calculating it when we setValue() and don't want to overwrite that // If the user supplies an argumnet for grouping, then on their own head be it if ($grouping !== null) { $this->setGrouping($grouping); } return $this; } /** * Get this Rule's AutoFilter Column Parent. * * @return ?Column */ public function getParent() { return $this->parent; } /** * Set this Rule's AutoFilter Column Parent. * * @return $this */ public function setParent(?Column $parent = null) { $this->setEvaluatedFalse(); $this->parent = $parent; return $this; } /** * 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) { if (is_object($value)) { if ($key == 'parent') { // this is only object // Detach from autofilter column parent $this->$key = null; } } else { $this->$key = $value; } } } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php000064400000022476152427537250021317 0ustar00columnIndex = $column; $this->parent = $parent; } public function setEvaluatedFalse(): void { if ($this->parent !== null) { $this->parent->setEvaluated(false); } } /** * Get AutoFilter column index as string eg: 'A'. * * @return string */ public function getColumnIndex() { return $this->columnIndex; } /** * Set AutoFilter column index as string eg: 'A'. * * @param string $column Column (e.g. A) * * @return $this */ public function setColumnIndex($column) { $this->setEvaluatedFalse(); // Uppercase coordinate $column = strtoupper($column); if ($this->parent !== null) { $this->parent->testColumnInRange($column); } $this->columnIndex = $column; return $this; } /** * Get this Column's AutoFilter Parent. * * @return null|AutoFilter */ public function getParent() { return $this->parent; } /** * Set this Column's AutoFilter Parent. * * @return $this */ public function setParent(?AutoFilter $parent = null) { $this->setEvaluatedFalse(); $this->parent = $parent; return $this; } /** * Get AutoFilter Type. * * @return string */ public function getFilterType() { return $this->filterType; } /** * Set AutoFilter Type. * * @param string $filterType * * @return $this */ public function setFilterType($filterType) { $this->setEvaluatedFalse(); if (!in_array($filterType, self::$filterTypes)) { throw new PhpSpreadsheetException('Invalid filter type for column AutoFilter.'); } if ($filterType === self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER && count($this->ruleset) > 2) { throw new PhpSpreadsheetException('No more than 2 rules are allowed in a Custom Filter'); } $this->filterType = $filterType; return $this; } /** * Get AutoFilter Multiple Rules And/Or Join. * * @return string */ public function getJoin() { return $this->join; } /** * Set AutoFilter Multiple Rules And/Or. * * @param string $join And/Or * * @return $this */ public function setJoin($join) { $this->setEvaluatedFalse(); // Lowercase And/Or $join = strtolower($join); if (!in_array($join, self::$ruleJoins)) { throw new PhpSpreadsheetException('Invalid rule connection for column AutoFilter.'); } $this->join = $join; return $this; } /** * Set AutoFilter Attributes. * * @param mixed[] $attributes * * @return $this */ public function setAttributes($attributes) { $this->setEvaluatedFalse(); $this->attributes = $attributes; return $this; } /** * Set An AutoFilter Attribute. * * @param string $name Attribute Name * @param int|string $value Attribute Value * * @return $this */ public function setAttribute($name, $value) { $this->setEvaluatedFalse(); $this->attributes[$name] = $value; return $this; } /** * Get AutoFilter Column Attributes. * * @return int[]|string[] */ public function getAttributes() { return $this->attributes; } /** * Get specific AutoFilter Column Attribute. * * @param string $name Attribute Name * * @return null|int|string */ public function getAttribute($name) { if (isset($this->attributes[$name])) { return $this->attributes[$name]; } return null; } public function ruleCount(): int { return count($this->ruleset); } /** * Get all AutoFilter Column Rules. * * @return Column\Rule[] */ public function getRules() { return $this->ruleset; } /** * Get a specified AutoFilter Column Rule. * * @param int $index Rule index in the ruleset array * * @return Column\Rule */ public function getRule($index) { if (!isset($this->ruleset[$index])) { $this->ruleset[$index] = new Column\Rule($this); } return $this->ruleset[$index]; } /** * Create a new AutoFilter Column Rule in the ruleset. * * @return Column\Rule */ public function createRule() { $this->setEvaluatedFalse(); if ($this->filterType === self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER && count($this->ruleset) >= 2) { throw new PhpSpreadsheetException('No more than 2 rules are allowed in a Custom Filter'); } $this->ruleset[] = new Column\Rule($this); return end($this->ruleset); } /** * Add a new AutoFilter Column Rule to the ruleset. * * @return $this */ public function addRule(Column\Rule $rule) { $this->setEvaluatedFalse(); $rule->setParent($this); $this->ruleset[] = $rule; return $this; } /** * Delete a specified AutoFilter Column Rule * If the number of rules is reduced to 1, then we reset And/Or logic to Or. * * @param int $index Rule index in the ruleset array * * @return $this */ public function deleteRule($index) { $this->setEvaluatedFalse(); if (isset($this->ruleset[$index])) { unset($this->ruleset[$index]); // If we've just deleted down to a single rule, then reset And/Or joining to Or if (count($this->ruleset) <= 1) { $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR); } } return $this; } /** * Delete all AutoFilter Column Rules. * * @return $this */ public function clearRules() { $this->setEvaluatedFalse(); $this->ruleset = []; $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR); return $this; } /** * 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) { if ($key === 'parent') { // Detach from autofilter parent $this->parent = null; } elseif ($key === 'ruleset') { // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects $this->ruleset = []; foreach ($value as $k => $v) { $cloned = clone $v; $cloned->setParent($this); // attach the new cloned Rule to this new cloned Autofilter Cloned object $this->ruleset[$k] = $cloned; } } else { $this->$key = $value; } } } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php000064400000013525152427537250021060 0ustar00 */ class RowCellIterator extends CellIterator { /** * Current iterator position. * * @var int */ private $currentColumnIndex; /** * Row index. * * @var int */ private $rowIndex = 1; /** * Start position. * * @var int */ private $startColumnIndex = 1; /** * End position. * * @var int */ private $endColumnIndex = 1; /** * Create a new column iterator. * * @param Worksheet $worksheet The worksheet to iterate over * @param int $rowIndex The row that we want to iterate * @param string $startColumn The column address at which to start iterating * @param string $endColumn Optionally, the column address at which to stop iterating */ public function __construct(Worksheet $worksheet, $rowIndex = 1, $startColumn = 'A', $endColumn = null) { // Set subject and row index $this->worksheet = $worksheet; $this->cellCollection = $worksheet->getCellCollection(); $this->rowIndex = $rowIndex; $this->resetEnd($endColumn); $this->resetStart($startColumn); } /** * (Re)Set the start column and the current column pointer. * * @param string $startColumn The column address at which to start iterating * * @return $this */ public function resetStart(string $startColumn = 'A') { $this->startColumnIndex = Coordinate::columnIndexFromString($startColumn); $this->adjustForExistingOnlyRange(); $this->seek(Coordinate::stringFromColumnIndex($this->startColumnIndex)); return $this; } /** * (Re)Set the end column. * * @param string $endColumn The column address at which to stop iterating * * @return $this */ public function resetEnd($endColumn = null) { $endColumn = $endColumn ?: $this->worksheet->getHighestColumn(); $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn); $this->adjustForExistingOnlyRange(); return $this; } /** * Set the column pointer to the selected column. * * @param string $column The column address to set the current pointer at * * @return $this */ public function seek(string $column = 'A') { $columnId = Coordinate::columnIndexFromString($column); if ($this->onlyExistingCells && !($this->cellCollection->has($column . $this->rowIndex))) { throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); } if (($columnId < $this->startColumnIndex) || ($columnId > $this->endColumnIndex)) { throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})"); } $this->currentColumnIndex = $columnId; return $this; } /** * Rewind the iterator to the starting column. */ public function rewind(): void { $this->currentColumnIndex = $this->startColumnIndex; } /** * Return the current cell in this worksheet row. */ public function current(): ?Cell { $cellAddress = Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex; return $this->cellCollection->has($cellAddress) ? $this->cellCollection->get($cellAddress) : ( $this->ifNotExists === self::IF_NOT_EXISTS_CREATE_NEW ? $this->worksheet->createNewCell($cellAddress) : null ); } /** * Return the current iterator key. */ public function key(): string { return Coordinate::stringFromColumnIndex($this->currentColumnIndex); } /** * Set the iterator to its next value. */ public function next(): void { do { ++$this->currentColumnIndex; } while (($this->onlyExistingCells) && (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex)) && ($this->currentColumnIndex <= $this->endColumnIndex)); } /** * Set the iterator to its previous value. */ public function prev(): void { do { --$this->currentColumnIndex; } while (($this->onlyExistingCells) && (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex)) && ($this->currentColumnIndex >= $this->startColumnIndex)); } /** * Indicate if more columns exist in the worksheet range of columns that we're iterating. */ public function valid(): bool { return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex; } /** * Return the current iterator position. */ public function getCurrentColumnIndex(): int { return $this->currentColumnIndex; } /** * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary. */ protected function adjustForExistingOnlyRange(): void { if ($this->onlyExistingCells) { while ((!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->startColumnIndex) . $this->rowIndex)) && ($this->startColumnIndex <= $this->endColumnIndex)) { ++$this->startColumnIndex; } while ((!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->endColumnIndex) . $this->rowIndex)) && ($this->endColumnIndex >= $this->startColumnIndex)) { --$this->endColumnIndex; } } } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php000064400000347727152427537250017770 0ustar00 */ private $drawingCollection; /** * Collection of Chart objects. * * @var ArrayObject */ private $chartCollection; /** * Collection of Table objects. * * @var ArrayObject */ private $tableCollection; /** * Worksheet title. * * @var string */ private $title; /** * Sheet state. * * @var string */ private $sheetState; /** * Page setup. * * @var PageSetup */ private $pageSetup; /** * Page margins. * * @var PageMargins */ private $pageMargins; /** * Page header/footer. * * @var HeaderFooter */ private $headerFooter; /** * Sheet view. * * @var SheetView */ private $sheetView; /** * Protection. * * @var Protection */ private $protection; /** * Collection of styles. * * @var Style[] */ private $styles = []; /** * Conditional styles. Indexed by cell coordinate, e.g. 'A1'. * * @var array */ private $conditionalStylesCollection = []; /** * Collection of row breaks. * * @var PageBreak[] */ private $rowBreaks = []; /** * Collection of column breaks. * * @var PageBreak[] */ private $columnBreaks = []; /** * Collection of merged cell ranges. * * @var string[] */ private $mergeCells = []; /** * Collection of protected cell ranges. * * @var string[] */ private $protectedCells = []; /** * Autofilter Range and selection. * * @var AutoFilter */ private $autoFilter; /** * Freeze pane. * * @var null|string */ private $freezePane; /** * Default position of the right bottom pane. * * @var null|string */ private $topLeftCell; /** * Show gridlines? * * @var bool */ private $showGridlines = true; /** * Print gridlines? * * @var bool */ private $printGridlines = false; /** * Show row and column headers? * * @var bool */ private $showRowColHeaders = true; /** * Show summary below? (Row/Column outline). * * @var bool */ private $showSummaryBelow = true; /** * Show summary right? (Row/Column outline). * * @var bool */ private $showSummaryRight = true; /** * Collection of comments. * * @var Comment[] */ private $comments = []; /** * Active cell. (Only one!). * * @var string */ private $activeCell = 'A1'; /** * Selected cells. * * @var string */ private $selectedCells = 'A1'; /** * Cached highest column. * * @var int */ private $cachedHighestColumn = 1; /** * Cached highest row. * * @var int */ private $cachedHighestRow = 1; /** * Right-to-left? * * @var bool */ private $rightToLeft = false; /** * Hyperlinks. Indexed by cell coordinate, e.g. 'A1'. * * @var array */ private $hyperlinkCollection = []; /** * Data validation objects. Indexed by cell coordinate, e.g. 'A1'. * * @var array */ private $dataValidationCollection = []; /** * Tab color. * * @var null|Color */ private $tabColor; /** * Dirty flag. * * @var bool */ private $dirty = true; /** * Hash. * * @var string */ private $hash; /** * CodeName. * * @var string */ private $codeName; /** * Create a new worksheet. * * @param string $title */ public function __construct(?Spreadsheet $parent = null, $title = 'Worksheet') { // Set parent and title $this->parent = $parent; $this->setTitle($title, false); // setTitle can change $pTitle $this->setCodeName($this->getTitle()); $this->setSheetState(self::SHEETSTATE_VISIBLE); $this->cellCollection = CellsFactory::getInstance($this); // Set page setup $this->pageSetup = new PageSetup(); // Set page margins $this->pageMargins = new PageMargins(); // Set page header/footer $this->headerFooter = new HeaderFooter(); // Set sheet view $this->sheetView = new SheetView(); // Drawing collection $this->drawingCollection = new ArrayObject(); // Chart collection $this->chartCollection = new ArrayObject(); // Protection $this->protection = new Protection(); // Default row dimension $this->defaultRowDimension = new RowDimension(null); // Default column dimension $this->defaultColumnDimension = new ColumnDimension(null); // AutoFilter $this->autoFilter = new AutoFilter('', $this); // Table collection $this->tableCollection = new ArrayObject(); } /** * Disconnect all cells from this Worksheet object, * typically so that the worksheet object can be unset. */ public function disconnectCells(): void { if ($this->cellCollection !== null) { $this->cellCollection->unsetWorksheetCells(); // @phpstan-ignore-next-line $this->cellCollection = null; } // detach ourself from the workbook, so that it can then delete this worksheet successfully $this->parent = null; } /** * Code to execute when this worksheet is unset(). */ public function __destruct() { Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title); $this->disconnectCells(); $this->rowDimensions = []; } /** * Return the cell collection. * * @return Cells */ public function getCellCollection() { return $this->cellCollection; } /** * Get array of invalid characters for sheet title. * * @return array */ public static function getInvalidCharacters() { return self::$invalidCharacters; } /** * Check sheet code name for valid Excel syntax. * * @param string $sheetCodeName The string to check * * @return string The valid string */ private static function checkSheetCodeName($sheetCodeName) { $charCount = Shared\StringHelper::countCharacters($sheetCodeName); if ($charCount == 0) { throw new Exception('Sheet code name cannot be empty.'); } // Some of the printable ASCII characters are invalid: * : / \ ? [ ] and first and last characters cannot be a "'" if ( (str_replace(self::$invalidCharacters, '', $sheetCodeName) !== $sheetCodeName) || (Shared\StringHelper::substring($sheetCodeName, -1, 1) == '\'') || (Shared\StringHelper::substring($sheetCodeName, 0, 1) == '\'') ) { throw new Exception('Invalid character found in sheet code name'); } // Enforce maximum characters allowed for sheet title if ($charCount > self::SHEET_TITLE_MAXIMUM_LENGTH) { throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet code name.'); } return $sheetCodeName; } /** * Check sheet title for valid Excel syntax. * * @param string $sheetTitle The string to check * * @return string The valid string */ private static function checkSheetTitle($sheetTitle) { // Some of the printable ASCII characters are invalid: * : / \ ? [ ] if (str_replace(self::$invalidCharacters, '', $sheetTitle) !== $sheetTitle) { throw new Exception('Invalid character found in sheet title'); } // Enforce maximum characters allowed for sheet title if (Shared\StringHelper::countCharacters($sheetTitle) > self::SHEET_TITLE_MAXIMUM_LENGTH) { throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet title.'); } return $sheetTitle; } /** * Get a sorted list of all cell coordinates currently held in the collection by row and column. * * @param bool $sorted Also sort the cell collection? * * @return string[] */ public function getCoordinates($sorted = true) { if ($this->cellCollection == null) { return []; } if ($sorted) { return $this->cellCollection->getSortedCoordinates(); } return $this->cellCollection->getCoordinates(); } /** * Get collection of row dimensions. * * @return RowDimension[] */ public function getRowDimensions() { return $this->rowDimensions; } /** * Get default row dimension. * * @return RowDimension */ public function getDefaultRowDimension() { return $this->defaultRowDimension; } /** * Get collection of column dimensions. * * @return ColumnDimension[] */ public function getColumnDimensions() { /** @var callable */ $callable = [self::class, 'columnDimensionCompare']; uasort($this->columnDimensions, $callable); return $this->columnDimensions; } private static function columnDimensionCompare(ColumnDimension $a, ColumnDimension $b): int { return $a->getColumnNumeric() - $b->getColumnNumeric(); } /** * Get default column dimension. * * @return ColumnDimension */ public function getDefaultColumnDimension() { return $this->defaultColumnDimension; } /** * Get collection of drawings. * * @return ArrayObject */ public function getDrawingCollection() { return $this->drawingCollection; } /** * Get collection of charts. * * @return ArrayObject */ public function getChartCollection() { return $this->chartCollection; } /** * Add chart. * * @param null|int $chartIndex Index where chart should go (0,1,..., or null for last) * * @return Chart */ public function addChart(Chart $chart, $chartIndex = null) { $chart->setWorksheet($this); if ($chartIndex === null) { $this->chartCollection[] = $chart; } else { // Insert the chart at the requested index // @phpstan-ignore-next-line array_splice(/** @scrutinizer ignore-type */ $this->chartCollection, $chartIndex, 0, [$chart]); } return $chart; } /** * Return the count of charts on this worksheet. * * @return int The number of charts */ public function getChartCount() { return count($this->chartCollection); } /** * Get a chart by its index position. * * @param ?string $index Chart index position * * @return Chart|false */ public function getChartByIndex($index) { $chartCount = count($this->chartCollection); if ($chartCount == 0) { return false; } if ($index === null) { $index = --$chartCount; } if (!isset($this->chartCollection[$index])) { return false; } return $this->chartCollection[$index]; } /** * Return an array of the names of charts on this worksheet. * * @return string[] The names of charts */ public function getChartNames() { $chartNames = []; foreach ($this->chartCollection as $chart) { $chartNames[] = $chart->getName(); } return $chartNames; } /** * Get a chart by name. * * @param string $chartName Chart name * * @return Chart|false */ public function getChartByName($chartName) { foreach ($this->chartCollection as $index => $chart) { if ($chart->getName() == $chartName) { return $chart; } } return false; } /** * Refresh column dimensions. * * @return $this */ public function refreshColumnDimensions() { $newColumnDimensions = []; foreach ($this->getColumnDimensions() as $objColumnDimension) { $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension; } $this->columnDimensions = $newColumnDimensions; return $this; } /** * Refresh row dimensions. * * @return $this */ public function refreshRowDimensions() { $newRowDimensions = []; foreach ($this->getRowDimensions() as $objRowDimension) { $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension; } $this->rowDimensions = $newRowDimensions; return $this; } /** * Calculate worksheet dimension. * * @return string String containing the dimension of this worksheet */ public function calculateWorksheetDimension() { // Return return 'A1:' . $this->getHighestColumn() . $this->getHighestRow(); } /** * Calculate worksheet data dimension. * * @return string String containing the dimension of this worksheet that actually contain data */ public function calculateWorksheetDataDimension() { // Return return 'A1:' . $this->getHighestDataColumn() . $this->getHighestDataRow(); } /** * Calculate widths for auto-size columns. * * @return $this */ public function calculateColumnWidths() { // initialize $autoSizes array $autoSizes = []; foreach ($this->getColumnDimensions() as $colDimension) { if ($colDimension->getAutoSize()) { $autoSizes[$colDimension->getColumnIndex()] = -1; } } // There is only something to do if there are some auto-size columns if (!empty($autoSizes)) { // build list of cells references that participate in a merge $isMergeCell = []; foreach ($this->getMergeCells() as $cells) { foreach (Coordinate::extractAllCellReferencesInRange($cells) as $cellReference) { $isMergeCell[$cellReference] = true; } } $autoFilterIndentRanges = (new AutoFit($this))->getAutoFilterIndentRanges(); // loop through all cells in the worksheet foreach ($this->getCoordinates(false) as $coordinate) { $cell = $this->getCellOrNull($coordinate); if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) { //Determine if cell is in merge range $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]); //By default merged cells should be ignored $isMergedButProceed = false; //The only exception is if it's a merge range value cell of a 'vertical' range (1 column wide) if ($isMerged && $cell->isMergeRangeValueCell()) { $range = (string) $cell->getMergeRange(); $rangeBoundaries = Coordinate::rangeDimension($range); if ($rangeBoundaries[0] === 1) { $isMergedButProceed = true; } } // Determine width if cell is not part of a merge or does and is a value cell of 1-column wide range if (!$isMerged || $isMergedButProceed) { // Determine if we need to make an adjustment for the first row in an AutoFilter range that // has a column filter dropdown $filterAdjustment = false; if (!empty($autoFilterIndentRanges)) { foreach ($autoFilterIndentRanges as $autoFilterFirstRowRange) { if ($cell->isInRange($autoFilterFirstRowRange)) { $filterAdjustment = true; break; } } } $indentAdjustment = $cell->getStyle()->getAlignment()->getIndent(); $indentAdjustment += (int) ($cell->getStyle()->getAlignment()->getHorizontal() === Alignment::HORIZONTAL_CENTER); // Calculated value // To formatted string $cellValue = NumberFormat::toFormattedString( $cell->getCalculatedValue(), (string) $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex()) ->getNumberFormat()->getFormatCode() ); if ($cellValue !== null && $cellValue !== '') { $autoSizes[$this->cellCollection->getCurrentColumn()] = max( $autoSizes[$this->cellCollection->getCurrentColumn()], round( Shared\Font::calculateColumnWidth( $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())->getFont(), $cellValue, (int) $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex()) ->getAlignment()->getTextRotation(), $this->getParentOrThrow()->getDefaultStyle()->getFont(), $filterAdjustment, $indentAdjustment ), 3 ) ); } } } } // adjust column widths foreach ($autoSizes as $columnIndex => $width) { if ($width == -1) { $width = $this->getDefaultColumnDimension()->getWidth(); } $this->getColumnDimension($columnIndex)->setWidth($width); } } return $this; } /** * Get parent or null. */ public function getParent(): ?Spreadsheet { return $this->parent; } /** * Get parent, throw exception if null. */ public function getParentOrThrow(): Spreadsheet { if ($this->parent !== null) { return $this->parent; } throw new Exception('Sheet does not have a parent.'); } /** * Re-bind parent. * * @return $this */ public function rebindParent(Spreadsheet $parent) { if ($this->parent !== null) { $definedNames = $this->parent->getDefinedNames(); foreach ($definedNames as $definedName) { $parent->addDefinedName($definedName); } $this->parent->removeSheetByIndex( $this->parent->getIndex($this) ); } $this->parent = $parent; return $this; } /** * Get title. * * @return string */ public function getTitle() { return $this->title; } /** * Set title. * * @param string $title String containing the dimension of this worksheet * @param bool $updateFormulaCellReferences Flag indicating whether cell references in formulae should * be updated to reflect the new sheet name. * This should be left as the default true, unless you are * certain that no formula cells on any worksheet contain * references to this worksheet * @param bool $validate False to skip validation of new title. WARNING: This should only be set * at parse time (by Readers), where titles can be assumed to be valid. * * @return $this */ public function setTitle($title, $updateFormulaCellReferences = true, $validate = true) { // Is this a 'rename' or not? if ($this->getTitle() == $title) { return $this; } // Old title $oldTitle = $this->getTitle(); if ($validate) { // Syntax check self::checkSheetTitle($title); if ($this->parent) { // Is there already such sheet name? if ($this->parent->sheetNameExists($title)) { // Use name, but append with lowest possible integer if (Shared\StringHelper::countCharacters($title) > 29) { $title = Shared\StringHelper::substring($title, 0, 29); } $i = 1; while ($this->parent->sheetNameExists($title . ' ' . $i)) { ++$i; if ($i == 10) { if (Shared\StringHelper::countCharacters($title) > 28) { $title = Shared\StringHelper::substring($title, 0, 28); } } elseif ($i == 100) { if (Shared\StringHelper::countCharacters($title) > 27) { $title = Shared\StringHelper::substring($title, 0, 27); } } } $title .= " $i"; } } } // Set title $this->title = $title; $this->dirty = true; if ($this->parent && $this->parent->getCalculationEngine()) { // New title $newTitle = $this->getTitle(); $this->parent->getCalculationEngine() ->renameCalculationCacheForWorksheet($oldTitle, $newTitle); if ($updateFormulaCellReferences) { ReferenceHelper::getInstance()->updateNamedFormulae($this->parent, $oldTitle, $newTitle); } } return $this; } /** * Get sheet state. * * @return string Sheet state (visible, hidden, veryHidden) */ public function getSheetState() { return $this->sheetState; } /** * Set sheet state. * * @param string $value Sheet state (visible, hidden, veryHidden) * * @return $this */ public function setSheetState($value) { $this->sheetState = $value; return $this; } /** * Get page setup. * * @return PageSetup */ public function getPageSetup() { return $this->pageSetup; } /** * Set page setup. * * @return $this */ public function setPageSetup(PageSetup $pageSetup) { $this->pageSetup = $pageSetup; return $this; } /** * Get page margins. * * @return PageMargins */ public function getPageMargins() { return $this->pageMargins; } /** * Set page margins. * * @return $this */ public function setPageMargins(PageMargins $pageMargins) { $this->pageMargins = $pageMargins; return $this; } /** * Get page header/footer. * * @return HeaderFooter */ public function getHeaderFooter() { return $this->headerFooter; } /** * Set page header/footer. * * @return $this */ public function setHeaderFooter(HeaderFooter $headerFooter) { $this->headerFooter = $headerFooter; return $this; } /** * Get sheet view. * * @return SheetView */ public function getSheetView() { return $this->sheetView; } /** * Set sheet view. * * @return $this */ public function setSheetView(SheetView $sheetView) { $this->sheetView = $sheetView; return $this; } /** * Get Protection. * * @return Protection */ public function getProtection() { return $this->protection; } /** * Set Protection. * * @return $this */ public function setProtection(Protection $protection) { $this->protection = $protection; $this->dirty = true; return $this; } /** * Get highest worksheet column. * * @param null|int|string $row Return the data highest column for the specified row, * or the highest column of any row if no row number is passed * * @return string Highest column name */ public function getHighestColumn($row = null) { if ($row === null) { return Coordinate::stringFromColumnIndex($this->cachedHighestColumn); } return $this->getHighestDataColumn($row); } /** * Get highest worksheet column that contains data. * * @param null|int|string $row Return the highest data column for the specified row, * or the highest data column of any row if no row number is passed * * @return string Highest column name that contains data */ public function getHighestDataColumn($row = null) { return $this->cellCollection->getHighestColumn($row); } /** * Get highest worksheet row. * * @param null|string $column Return the highest data row for the specified column, * or the highest row of any column if no column letter is passed * * @return int Highest row number */ public function getHighestRow($column = null) { if ($column === null) { return $this->cachedHighestRow; } return $this->getHighestDataRow($column); } /** * Get highest worksheet row that contains data. * * @param null|string $column Return the highest data row for the specified column, * or the highest data row of any column if no column letter is passed * * @return int Highest row number that contains data */ public function getHighestDataRow($column = null) { return $this->cellCollection->getHighestRow($column); } /** * Get highest worksheet column and highest row that have cell records. * * @return array Highest column name and highest row number */ public function getHighestRowAndColumn() { return $this->cellCollection->getHighestRowAndColumn(); } /** * Set a cell value. * * @param array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @param mixed $value Value for the cell * @param null|IValueBinder $binder Value Binder to override the currently set Value Binder * * @return $this */ public function setCellValue($coordinate, $value, ?IValueBinder $binder = null) { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); $this->getCell($cellAddress)->setValue($value, $binder); return $this; } /** * Set a cell value by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the setCellValue() method with a cell address such as 'C5' instead;, * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @see Worksheet::setCellValue() * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * @param mixed $value Value of the cell * @param null|IValueBinder $binder Value Binder to override the currently set Value Binder * * @return $this */ public function setCellValueByColumnAndRow($columnIndex, $row, $value, ?IValueBinder $binder = null) { $this->getCell(Coordinate::stringFromColumnIndex($columnIndex) . $row)->setValue($value, $binder); return $this; } /** * Set a cell value. * * @param array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @param mixed $value Value of the cell * @param string $dataType Explicit data type, see DataType::TYPE_* * Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this * method, then it is your responsibility as an end-user developer to validate that the value and * the datatype match. * If you do mismatch value and datatpe, then the value you enter may be changed to match the datatype * that you specify. * * @see DataType * * @return $this */ public function setCellValueExplicit($coordinate, $value, $dataType) { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); $this->getCell($cellAddress)->setValueExplicit($value, $dataType); return $this; } /** * Set a cell value by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the setCellValueExplicit() method with a cell address such as 'C5' instead;, * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @see Worksheet::setCellValueExplicit() * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * @param mixed $value Value of the cell * @param string $dataType Explicit data type, see DataType::TYPE_* * Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this * method, then it is your responsibility as an end-user developer to validate that the value and * the datatype match. * If you do mismatch value and datatpe, then the value you enter may be changed to match the datatype * that you specify. * * @see DataType * * @return $this */ public function setCellValueExplicitByColumnAndRow($columnIndex, $row, $value, $dataType) { $this->getCell(Coordinate::stringFromColumnIndex($columnIndex) . $row)->setValueExplicit($value, $dataType); return $this; } /** * Get cell at a specific coordinate. * * @param array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * * @return Cell Cell that was found or created * WARNING: Because the cell collection can be cached to reduce memory, it only allows one * "active" cell at a time in memory. If you assign that cell to a variable, then select * another cell using getCell() or any of its variants, the newly selected cell becomes * the "active" cell, and any previous assignment becomes a disconnected reference because * the active cell has changed. */ public function getCell($coordinate): Cell { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); // Shortcut for increased performance for the vast majority of simple cases if ($this->cellCollection->has($cellAddress)) { /** @var Cell $cell */ $cell = $this->cellCollection->get($cellAddress); return $cell; } /** @var Worksheet $sheet */ [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($cellAddress); $cell = $sheet->cellCollection->get($finalCoordinate); return $cell ?? $sheet->createNewCell($finalCoordinate); } /** * Get the correct Worksheet and coordinate from a coordinate that may * contains reference to another sheet or a named range. * * @return array{0: Worksheet, 1: string} */ private function getWorksheetAndCoordinate(string $coordinate): array { $sheet = null; $finalCoordinate = null; // Worksheet reference? if (strpos($coordinate, '!') !== false) { $worksheetReference = self::extractSheetTitle($coordinate, true); $sheet = $this->getParentOrThrow()->getSheetByName($worksheetReference[0]); $finalCoordinate = strtoupper($worksheetReference[1]); if ($sheet === null) { throw new Exception('Sheet not found for name: ' . $worksheetReference[0]); } } elseif ( !preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $coordinate) && preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/iu', $coordinate) ) { // Named range? $namedRange = $this->validateNamedRange($coordinate, true); if ($namedRange !== null) { $sheet = $namedRange->getWorksheet(); if ($sheet === null) { throw new Exception('Sheet not found for named range: ' . $namedRange->getName()); } /** @phpstan-ignore-next-line */ $cellCoordinate = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!'); $finalCoordinate = str_replace('$', '', $cellCoordinate); } } if ($sheet === null || $finalCoordinate === null) { $sheet = $this; $finalCoordinate = strtoupper($coordinate); } if (Coordinate::coordinateIsRange($finalCoordinate)) { throw new Exception('Cell coordinate string can not be a range of cells.'); } elseif (strpos($finalCoordinate, '$') !== false) { throw new Exception('Cell coordinate must not be absolute.'); } return [$sheet, $finalCoordinate]; } /** * Get an existing cell at a specific coordinate, or null. * * @param string $coordinate Coordinate of the cell, eg: 'A1' * * @return null|Cell Cell that was found or null */ private function getCellOrNull($coordinate): ?Cell { // Check cell collection if ($this->cellCollection->has($coordinate)) { return $this->cellCollection->get($coordinate); } return null; } /** * Get cell at a specific coordinate by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the getCell() method with a cell address such as 'C5' instead;, * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @see Worksheet::getCell() * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * * @return Cell Cell that was found/created or null * WARNING: Because the cell collection can be cached to reduce memory, it only allows one * "active" cell at a time in memory. If you assign that cell to a variable, then select * another cell using getCell() or any of its variants, the newly selected cell becomes * the "active" cell, and any previous assignment becomes a disconnected reference because * the active cell has changed. */ public function getCellByColumnAndRow($columnIndex, $row): Cell { return $this->getCell(Coordinate::stringFromColumnIndex($columnIndex) . $row); } /** * Create a new cell at the specified coordinate. * * @param string $coordinate Coordinate of the cell * * @return Cell Cell that was created * WARNING: Because the cell collection can be cached to reduce memory, it only allows one * "active" cell at a time in memory. If you assign that cell to a variable, then select * another cell using getCell() or any of its variants, the newly selected cell becomes * the "active" cell, and any previous assignment becomes a disconnected reference because * the active cell has changed. */ public function createNewCell($coordinate): Cell { [$column, $row, $columnString] = Coordinate::indexesFromString($coordinate); $cell = new Cell(null, DataType::TYPE_NULL, $this); $this->cellCollection->add($coordinate, $cell); // Coordinates if ($column > $this->cachedHighestColumn) { $this->cachedHighestColumn = $column; } if ($row > $this->cachedHighestRow) { $this->cachedHighestRow = $row; } // Cell needs appropriate xfIndex from dimensions records // but don't create dimension records if they don't already exist $rowDimension = $this->rowDimensions[$row] ?? null; $columnDimension = $this->columnDimensions[$columnString] ?? null; if ($rowDimension !== null) { $rowXf = (int) $rowDimension->getXfIndex(); if ($rowXf > 0) { // then there is a row dimension with explicit style, assign it to the cell $cell->setXfIndex($rowXf); } } elseif ($columnDimension !== null) { $colXf = (int) $columnDimension->getXfIndex(); if ($colXf > 0) { // then there is a column dimension, assign it to the cell $cell->setXfIndex($colXf); } } return $cell; } /** * Does the cell at a specific coordinate exist? * * @param array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. */ public function cellExists($coordinate): bool { $cellAddress = Validations::validateCellAddress($coordinate); /** @var Worksheet $sheet */ [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($cellAddress); return $sheet->cellCollection->has($finalCoordinate); } /** * Cell at a specific coordinate by using numeric cell coordinates exists? * * @deprecated 1.23.0 * Use the cellExists() method with a cell address such as 'C5' instead;, * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @see Worksheet::cellExists() * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell */ public function cellExistsByColumnAndRow($columnIndex, $row): bool { return $this->cellExists(Coordinate::stringFromColumnIndex($columnIndex) . $row); } /** * Get row dimension at a specific row. * * @param int $row Numeric index of the row */ public function getRowDimension(int $row): RowDimension { // Get row dimension if (!isset($this->rowDimensions[$row])) { $this->rowDimensions[$row] = new RowDimension($row); $this->cachedHighestRow = max($this->cachedHighestRow, $row); } return $this->rowDimensions[$row]; } public function rowDimensionExists(int $row): bool { return isset($this->rowDimensions[$row]); } /** * Get column dimension at a specific column. * * @param string $column String index of the column eg: 'A' */ public function getColumnDimension(string $column): ColumnDimension { // Uppercase coordinate $column = strtoupper($column); // Fetch dimensions if (!isset($this->columnDimensions[$column])) { $this->columnDimensions[$column] = new ColumnDimension($column); $columnIndex = Coordinate::columnIndexFromString($column); if ($this->cachedHighestColumn < $columnIndex) { $this->cachedHighestColumn = $columnIndex; } } return $this->columnDimensions[$column]; } /** * Get column dimension at a specific column by using numeric cell coordinates. * * @param int $columnIndex Numeric column coordinate of the cell */ public function getColumnDimensionByColumn(int $columnIndex): ColumnDimension { return $this->getColumnDimension(Coordinate::stringFromColumnIndex($columnIndex)); } /** * Get styles. * * @return Style[] */ public function getStyles() { return $this->styles; } /** * Get style for cell. * * @param AddressRange|array|CellAddress|int|string $cellCoordinate * A simple string containing a cell address like 'A1' or a cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or a CellAddress or AddressRange object. */ public function getStyle($cellCoordinate): Style { $cellCoordinate = Validations::validateCellOrCellRange($cellCoordinate); // set this sheet as active $this->getParentOrThrow()->setActiveSheetIndex($this->getParentOrThrow()->getIndex($this)); // set cell coordinate as active $this->setSelectedCells($cellCoordinate); return $this->getParentOrThrow()->getCellXfSupervisor(); } /** * Get style for cell by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the getStyle() method with a cell address range such as 'C5:F8' instead;, * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. * @see Worksheet::getStyle() * * @param int $columnIndex1 Numeric column coordinate of the cell * @param int $row1 Numeric row coordinate of the cell * @param null|int $columnIndex2 Numeric column coordinate of the range cell * @param null|int $row2 Numeric row coordinate of the range cell * * @return Style */ public function getStyleByColumnAndRow($columnIndex1, $row1, $columnIndex2 = null, $row2 = null) { if ($columnIndex2 !== null && $row2 !== null) { $cellRange = new CellRange( CellAddress::fromColumnAndRow($columnIndex1, $row1), CellAddress::fromColumnAndRow($columnIndex2, $row2) ); return $this->getStyle($cellRange); } return $this->getStyle(CellAddress::fromColumnAndRow($columnIndex1, $row1)); } /** * Get conditional styles for a cell. * * @param string $coordinate eg: 'A1' or 'A1:A3'. * If a single cell is referenced, then the array of conditional styles will be returned if the cell is * included in a conditional style range. * If a range of cells is specified, then the styles will only be returned if the range matches the entire * range of the conditional. * * @return Conditional[] */ public function getConditionalStyles(string $coordinate): array { $coordinate = strtoupper($coordinate); if (strpos($coordinate, ':') !== false) { return $this->conditionalStylesCollection[$coordinate] ?? []; } $cell = $this->getCell($coordinate); foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) { if ($cell->isInRange($conditionalRange)) { return $this->conditionalStylesCollection[$conditionalRange]; } } return []; } public function getConditionalRange(string $coordinate): ?string { $coordinate = strtoupper($coordinate); $cell = $this->getCell($coordinate); foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) { if ($cell->isInRange($conditionalRange)) { return $conditionalRange; } } return null; } /** * Do conditional styles exist for this cell? * * @param string $coordinate eg: 'A1' or 'A1:A3'. * If a single cell is specified, then this method will return true if that cell is included in a * conditional style range. * If a range of cells is specified, then true will only be returned if the range matches the entire * range of the conditional. */ public function conditionalStylesExists($coordinate): bool { $coordinate = strtoupper($coordinate); if (strpos($coordinate, ':') !== false) { return isset($this->conditionalStylesCollection[$coordinate]); } $cell = $this->getCell($coordinate); foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) { if ($cell->isInRange($conditionalRange)) { return true; } } return false; } /** * Removes conditional styles for a cell. * * @param string $coordinate eg: 'A1' * * @return $this */ public function removeConditionalStyles($coordinate) { unset($this->conditionalStylesCollection[strtoupper($coordinate)]); return $this; } /** * Get collection of conditional styles. * * @return array */ public function getConditionalStylesCollection() { return $this->conditionalStylesCollection; } /** * Set conditional styles. * * @param string $coordinate eg: 'A1' * @param Conditional[] $styles * * @return $this */ public function setConditionalStyles($coordinate, $styles) { $this->conditionalStylesCollection[strtoupper($coordinate)] = $styles; return $this; } /** * Duplicate cell style to a range of cells. * * Please note that this will overwrite existing cell styles for cells in range! * * @param Style $style Cell style to duplicate * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") * * @return $this */ public function duplicateStyle(Style $style, $range) { // Add the style to the workbook if necessary $workbook = $this->getParentOrThrow(); if ($existingStyle = $workbook->getCellXfByHashCode($style->getHashCode())) { // there is already such cell Xf in our collection $xfIndex = $existingStyle->getIndex(); } else { // we don't have such a cell Xf, need to add $workbook->addCellXf($style); $xfIndex = $style->getIndex(); } // Calculate range outer borders [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range . ':' . $range); // Make sure we can loop upwards on rows and columns if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { $tmp = $rangeStart; $rangeStart = $rangeEnd; $rangeEnd = $tmp; } // Loop through cells and apply styles for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { $this->getCell(Coordinate::stringFromColumnIndex($col) . $row)->setXfIndex($xfIndex); } } return $this; } /** * Duplicate conditional style to a range of cells. * * Please note that this will overwrite existing cell styles for cells in range! * * @param Conditional[] $styles Cell style to duplicate * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") * * @return $this */ public function duplicateConditionalStyle(array $styles, $range = '') { foreach ($styles as $cellStyle) { if (!($cellStyle instanceof Conditional)) { throw new Exception('Style is not a conditional style'); } } // Calculate range outer borders [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range . ':' . $range); // Make sure we can loop upwards on rows and columns if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { $tmp = $rangeStart; $rangeStart = $rangeEnd; $rangeEnd = $tmp; } // Loop through cells and apply styles for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { $this->setConditionalStyles(Coordinate::stringFromColumnIndex($col) . $row, $styles); } } return $this; } /** * Set break on a cell. * * @param array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @param int $break Break type (type of Worksheet::BREAK_*) * * @return $this */ public function setBreak($coordinate, $break, int $max = -1) { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); if ($break === self::BREAK_NONE) { unset($this->rowBreaks[$cellAddress], $this->columnBreaks[$cellAddress]); } elseif ($break === self::BREAK_ROW) { $this->rowBreaks[$cellAddress] = new PageBreak($break, $cellAddress, $max); } elseif ($break === self::BREAK_COLUMN) { $this->columnBreaks[$cellAddress] = new PageBreak($break, $cellAddress, $max); } return $this; } /** * Set break on a cell by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the setBreak() method with a cell address such as 'C5' instead;, * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @see Worksheet::setBreak() * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * @param int $break Break type (type of Worksheet::BREAK_*) * * @return $this */ public function setBreakByColumnAndRow($columnIndex, $row, $break) { return $this->setBreak(Coordinate::stringFromColumnIndex($columnIndex) . $row, $break); } /** * Get breaks. * * @return int[] */ public function getBreaks() { $breaks = []; /** @var callable */ $compareFunction = [self::class, 'compareRowBreaks']; uksort($this->rowBreaks, $compareFunction); foreach ($this->rowBreaks as $break) { $breaks[$break->getCoordinate()] = self::BREAK_ROW; } /** @var callable */ $compareFunction = [self::class, 'compareColumnBreaks']; uksort($this->columnBreaks, $compareFunction); foreach ($this->columnBreaks as $break) { $breaks[$break->getCoordinate()] = self::BREAK_COLUMN; } return $breaks; } /** * Get row breaks. * * @return PageBreak[] */ public function getRowBreaks() { /** @var callable */ $compareFunction = [self::class, 'compareRowBreaks']; uksort($this->rowBreaks, $compareFunction); return $this->rowBreaks; } protected static function compareRowBreaks(string $coordinate1, string $coordinate2): int { $row1 = Coordinate::indexesFromString($coordinate1)[1]; $row2 = Coordinate::indexesFromString($coordinate2)[1]; return $row1 - $row2; } protected static function compareColumnBreaks(string $coordinate1, string $coordinate2): int { $column1 = Coordinate::indexesFromString($coordinate1)[0]; $column2 = Coordinate::indexesFromString($coordinate2)[0]; return $column1 - $column2; } /** * Get column breaks. * * @return PageBreak[] */ public function getColumnBreaks() { /** @var callable */ $compareFunction = [self::class, 'compareColumnBreaks']; uksort($this->columnBreaks, $compareFunction); return $this->columnBreaks; } /** * Set merge on a cell range. * * @param AddressRange|array|string $range A simple string containing a Cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange. * @param string $behaviour How the merged cells should behave. * Possible values are: * MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells * MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells * MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell * * @return $this */ public function mergeCells($range, $behaviour = self::MERGE_CELL_CONTENT_EMPTY) { $range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range)); if (strpos($range, ':') === false) { $range .= ":{$range}"; } if (preg_match('/^([A-Z]+)(\\d+):([A-Z]+)(\\d+)$/', $range, $matches) !== 1) { throw new Exception('Merge must be on a valid range of cells.'); } $this->mergeCells[$range] = $range; $firstRow = (int) $matches[2]; $lastRow = (int) $matches[4]; $firstColumn = $matches[1]; $lastColumn = $matches[3]; $firstColumnIndex = Coordinate::columnIndexFromString($firstColumn); $lastColumnIndex = Coordinate::columnIndexFromString($lastColumn); $numberRows = $lastRow - $firstRow; $numberColumns = $lastColumnIndex - $firstColumnIndex; if ($numberRows === 1 && $numberColumns === 1) { return $this; } // create upper left cell if it does not already exist $upperLeft = "{$firstColumn}{$firstRow}"; if (!$this->cellExists($upperLeft)) { $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL); } if ($behaviour !== self::MERGE_CELL_CONTENT_HIDE) { // Blank out the rest of the cells in the range (if they exist) if ($numberRows > $numberColumns) { $this->clearMergeCellsByColumn($firstColumn, $lastColumn, $firstRow, $lastRow, $upperLeft, $behaviour); } else { $this->clearMergeCellsByRow($firstColumn, $lastColumnIndex, $firstRow, $lastRow, $upperLeft, $behaviour); } } return $this; } private function clearMergeCellsByColumn(string $firstColumn, string $lastColumn, int $firstRow, int $lastRow, string $upperLeft, string $behaviour): void { $leftCellValue = ($behaviour === self::MERGE_CELL_CONTENT_MERGE) ? [$this->getCell($upperLeft)->getFormattedValue()] : []; foreach ($this->getColumnIterator($firstColumn, $lastColumn) as $column) { $iterator = $column->getCellIterator($firstRow); $iterator->setIterateOnlyExistingCells(true); foreach ($iterator as $cell) { if ($cell !== null) { $row = $cell->getRow(); if ($row > $lastRow) { break; } $leftCellValue = $this->mergeCellBehaviour($cell, $upperLeft, $behaviour, $leftCellValue); } } } if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { $this->getCell($upperLeft)->setValueExplicit(implode(' ', $leftCellValue), DataType::TYPE_STRING); } } private function clearMergeCellsByRow(string $firstColumn, int $lastColumnIndex, int $firstRow, int $lastRow, string $upperLeft, string $behaviour): void { $leftCellValue = ($behaviour === self::MERGE_CELL_CONTENT_MERGE) ? [$this->getCell($upperLeft)->getFormattedValue()] : []; foreach ($this->getRowIterator($firstRow, $lastRow) as $row) { $iterator = $row->getCellIterator($firstColumn); $iterator->setIterateOnlyExistingCells(true); foreach ($iterator as $cell) { if ($cell !== null) { $column = $cell->getColumn(); $columnIndex = Coordinate::columnIndexFromString($column); if ($columnIndex > $lastColumnIndex) { break; } $leftCellValue = $this->mergeCellBehaviour($cell, $upperLeft, $behaviour, $leftCellValue); } } } if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { $this->getCell($upperLeft)->setValueExplicit(implode(' ', $leftCellValue), DataType::TYPE_STRING); } } public function mergeCellBehaviour(Cell $cell, string $upperLeft, string $behaviour, array $leftCellValue): array { if ($cell->getCoordinate() !== $upperLeft) { Calculation::getInstance($cell->getWorksheet()->getParentOrThrow())->flushInstance(); if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { $cellValue = $cell->getFormattedValue(); if ($cellValue !== '') { $leftCellValue[] = $cellValue; } } $cell->setValueExplicit(null, DataType::TYPE_NULL); } return $leftCellValue; } /** * Set merge on a cell range by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the mergeCells() method with a cell address range such as 'C5:F8' instead;, * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. * @see Worksheet::mergeCells() * * @param int $columnIndex1 Numeric column coordinate of the first cell * @param int $row1 Numeric row coordinate of the first cell * @param int $columnIndex2 Numeric column coordinate of the last cell * @param int $row2 Numeric row coordinate of the last cell * @param string $behaviour How the merged cells should behave. * Possible values are: * MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells * MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells * MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell * * @return $this */ public function mergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2, $behaviour = self::MERGE_CELL_CONTENT_EMPTY) { $cellRange = new CellRange( CellAddress::fromColumnAndRow($columnIndex1, $row1), CellAddress::fromColumnAndRow($columnIndex2, $row2) ); return $this->mergeCells($cellRange, $behaviour); } /** * Remove merge on a cell range. * * @param AddressRange|array|string $range A simple string containing a Cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange. * * @return $this */ public function unmergeCells($range) { $range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range)); if (strpos($range, ':') !== false) { if (isset($this->mergeCells[$range])) { unset($this->mergeCells[$range]); } else { throw new Exception('Cell range ' . $range . ' not known as merged.'); } } else { throw new Exception('Merge can only be removed from a range of cells.'); } return $this; } /** * Remove merge on a cell range by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the unmergeCells() method with a cell address range such as 'C5:F8' instead;, * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. * @see Worksheet::unmergeCells() * * @param int $columnIndex1 Numeric column coordinate of the first cell * @param int $row1 Numeric row coordinate of the first cell * @param int $columnIndex2 Numeric column coordinate of the last cell * @param int $row2 Numeric row coordinate of the last cell * * @return $this */ public function unmergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) { $cellRange = new CellRange( CellAddress::fromColumnAndRow($columnIndex1, $row1), CellAddress::fromColumnAndRow($columnIndex2, $row2) ); return $this->unmergeCells($cellRange); } /** * Get merge cells array. * * @return string[] */ public function getMergeCells() { return $this->mergeCells; } /** * Set merge cells array for the entire sheet. Use instead mergeCells() to merge * a single cell range. * * @param string[] $mergeCells * * @return $this */ public function setMergeCells(array $mergeCells) { $this->mergeCells = $mergeCells; return $this; } /** * Set protection on a cell or cell range. * * @param AddressRange|array|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or a CellAddress or AddressRange object. * @param string $password Password to unlock the protection * @param bool $alreadyHashed If the password has already been hashed, set this to true * * @return $this */ public function protectCells($range, $password, $alreadyHashed = false) { $range = Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range)); if (!$alreadyHashed) { $password = Shared\PasswordHasher::hashPassword($password); } $this->protectedCells[$range] = $password; return $this; } /** * Set protection on a cell range by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the protectCells() method with a cell address range such as 'C5:F8' instead;, * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. * @see Worksheet::protectCells() * * @param int $columnIndex1 Numeric column coordinate of the first cell * @param int $row1 Numeric row coordinate of the first cell * @param int $columnIndex2 Numeric column coordinate of the last cell * @param int $row2 Numeric row coordinate of the last cell * @param string $password Password to unlock the protection * @param bool $alreadyHashed If the password has already been hashed, set this to true * * @return $this */ public function protectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2, $password, $alreadyHashed = false) { $cellRange = new CellRange( CellAddress::fromColumnAndRow($columnIndex1, $row1), CellAddress::fromColumnAndRow($columnIndex2, $row2) ); return $this->protectCells($cellRange, $password, $alreadyHashed); } /** * Remove protection on a cell or cell range. * * @param AddressRange|array|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or a CellAddress or AddressRange object. * * @return $this */ public function unprotectCells($range) { $range = Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range)); if (isset($this->protectedCells[$range])) { unset($this->protectedCells[$range]); } else { throw new Exception('Cell range ' . $range . ' not known as protected.'); } return $this; } /** * Remove protection on a cell range by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the unprotectCells() method with a cell address range such as 'C5:F8' instead;, * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. * @see Worksheet::unprotectCells() * * @param int $columnIndex1 Numeric column coordinate of the first cell * @param int $row1 Numeric row coordinate of the first cell * @param int $columnIndex2 Numeric column coordinate of the last cell * @param int $row2 Numeric row coordinate of the last cell * * @return $this */ public function unprotectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) { $cellRange = new CellRange( CellAddress::fromColumnAndRow($columnIndex1, $row1), CellAddress::fromColumnAndRow($columnIndex2, $row2) ); return $this->unprotectCells($cellRange); } /** * Get protected cells. * * @return string[] */ public function getProtectedCells() { return $this->protectedCells; } /** * Get Autofilter. * * @return AutoFilter */ public function getAutoFilter() { return $this->autoFilter; } /** * Set AutoFilter. * * @param AddressRange|array|AutoFilter|string $autoFilterOrRange * A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange. * * @return $this */ public function setAutoFilter($autoFilterOrRange) { if (is_object($autoFilterOrRange) && ($autoFilterOrRange instanceof AutoFilter)) { $this->autoFilter = $autoFilterOrRange; } else { $cellRange = Functions::trimSheetFromCellReference(Validations::validateCellRange($autoFilterOrRange)); $this->autoFilter->setRange($cellRange); } return $this; } /** * Set Autofilter Range by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the setAutoFilter() method with a cell address range such as 'C5:F8' instead;, * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object or AutoFilter object. * @see Worksheet::setAutoFilter() * * @param int $columnIndex1 Numeric column coordinate of the first cell * @param int $row1 Numeric row coordinate of the first cell * @param int $columnIndex2 Numeric column coordinate of the second cell * @param int $row2 Numeric row coordinate of the second cell * * @return $this */ public function setAutoFilterByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) { $cellRange = new CellRange( CellAddress::fromColumnAndRow($columnIndex1, $row1), CellAddress::fromColumnAndRow($columnIndex2, $row2) ); return $this->setAutoFilter($cellRange); } /** * Remove autofilter. */ public function removeAutoFilter(): self { $this->autoFilter->setRange(''); return $this; } /** * Get collection of Tables. * * @return ArrayObject */ public function getTableCollection() { return $this->tableCollection; } /** * Add Table. * * @return $this */ public function addTable(Table $table): self { $table->setWorksheet($this); $this->tableCollection[] = $table; return $this; } /** * @return string[] array of Table names */ public function getTableNames(): array { $tableNames = []; foreach ($this->tableCollection as $table) { /** @var Table $table */ $tableNames[] = $table->getName(); } return $tableNames; } /** @var null|Table */ private static $scrutinizerNullTable; /** @var null|int */ private static $scrutinizerNullInt; /** * @param string $name the table name to search * * @return null|Table The table from the tables collection, or null if not found */ public function getTableByName(string $name): ?Table { $tableIndex = $this->getTableIndexByName($name); return ($tableIndex === null) ? self::$scrutinizerNullTable : $this->tableCollection[$tableIndex]; } /** * @param string $name the table name to search * * @return null|int The index of the located table in the tables collection, or null if not found */ protected function getTableIndexByName(string $name): ?int { $name = Shared\StringHelper::strToUpper($name); foreach ($this->tableCollection as $index => $table) { /** @var Table $table */ if (Shared\StringHelper::strToUpper($table->getName()) === $name) { return $index; } } return self::$scrutinizerNullInt; } /** * Remove Table by name. * * @param string $name Table name * * @return $this */ public function removeTableByName(string $name): self { $tableIndex = $this->getTableIndexByName($name); if ($tableIndex !== null) { unset($this->tableCollection[$tableIndex]); } return $this; } /** * Remove collection of Tables. */ public function removeTableCollection(): self { $this->tableCollection = new ArrayObject(); return $this; } /** * Get Freeze Pane. * * @return null|string */ public function getFreezePane() { return $this->freezePane; } /** * Freeze Pane. * * Examples: * * - A2 will freeze the rows above cell A2 (i.e row 1) * - B1 will freeze the columns to the left of cell B1 (i.e column A) * - B2 will freeze the rows above and to the left of cell B2 (i.e row 1 and column A) * * @param null|array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * Passing a null value for this argument will clear any existing freeze pane for this worksheet. * @param null|array|CellAddress|string $topLeftCell default position of the right bottom pane * Coordinate of the cell as a string, eg: 'C5'; or as an array of [$columnIndex, $row] (e.g. [3, 5]), * or a CellAddress object. * * @return $this */ public function freezePane($coordinate, $topLeftCell = null) { $cellAddress = ($coordinate !== null) ? Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)) : null; if ($cellAddress !== null && Coordinate::coordinateIsRange($cellAddress)) { throw new Exception('Freeze pane can not be set on a range of cells.'); } $topLeftCell = ($topLeftCell !== null) ? Functions::trimSheetFromCellReference(Validations::validateCellAddress($topLeftCell)) : null; if ($cellAddress !== null && $topLeftCell === null) { $coordinate = Coordinate::coordinateFromString($cellAddress); $topLeftCell = $coordinate[0] . $coordinate[1]; } $this->freezePane = $cellAddress; $this->topLeftCell = $topLeftCell; return $this; } public function setTopLeftCell(string $topLeftCell): self { $this->topLeftCell = $topLeftCell; return $this; } /** * Freeze Pane by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the freezePane() method with a cell address such as 'C5' instead;, * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @see Worksheet::freezePane() * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * * @return $this */ public function freezePaneByColumnAndRow($columnIndex, $row) { return $this->freezePane(Coordinate::stringFromColumnIndex($columnIndex) . $row); } /** * Unfreeze Pane. * * @return $this */ public function unfreezePane() { return $this->freezePane(null); } /** * Get the default position of the right bottom pane. * * @return null|string */ public function getTopLeftCell() { return $this->topLeftCell; } /** * Insert a new row, updating all possible related data. * * @param int $before Insert before this row number * @param int $numberOfRows Number of new rows to insert * * @return $this */ public function insertNewRowBefore(int $before, int $numberOfRows = 1) { if ($before >= 1) { $objReferenceHelper = ReferenceHelper::getInstance(); $objReferenceHelper->insertNewBefore('A' . $before, 0, $numberOfRows, $this); } else { throw new Exception('Rows can only be inserted before at least row 1.'); } return $this; } /** * Insert a new column, updating all possible related data. * * @param string $before Insert before this column Name, eg: 'A' * @param int $numberOfColumns Number of new columns to insert * * @return $this */ public function insertNewColumnBefore(string $before, int $numberOfColumns = 1) { if (!is_numeric($before)) { $objReferenceHelper = ReferenceHelper::getInstance(); $objReferenceHelper->insertNewBefore($before . '1', $numberOfColumns, 0, $this); } else { throw new Exception('Column references should not be numeric.'); } return $this; } /** * Insert a new column, updating all possible related data. * * @param int $beforeColumnIndex Insert before this column ID (numeric column coordinate of the cell) * @param int $numberOfColumns Number of new columns to insert * * @return $this */ public function insertNewColumnBeforeByIndex(int $beforeColumnIndex, int $numberOfColumns = 1) { if ($beforeColumnIndex >= 1) { return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $numberOfColumns); } throw new Exception('Columns can only be inserted before at least column A (1).'); } /** * Delete a row, updating all possible related data. * * @param int $row Remove rows, starting with this row number * @param int $numberOfRows Number of rows to remove * * @return $this */ public function removeRow(int $row, int $numberOfRows = 1) { if ($row < 1) { throw new Exception('Rows to be deleted should at least start from row 1.'); } $holdRowDimensions = $this->removeRowDimensions($row, $numberOfRows); $highestRow = $this->getHighestDataRow(); $removedRowsCounter = 0; for ($r = 0; $r < $numberOfRows; ++$r) { if ($row + $r <= $highestRow) { $this->getCellCollection()->removeRow($row + $r); ++$removedRowsCounter; } } $objReferenceHelper = ReferenceHelper::getInstance(); $objReferenceHelper->insertNewBefore('A' . ($row + $numberOfRows), 0, -$numberOfRows, $this); for ($r = 0; $r < $removedRowsCounter; ++$r) { $this->getCellCollection()->removeRow($highestRow); --$highestRow; } $this->rowDimensions = $holdRowDimensions; return $this; } private function removeRowDimensions(int $row, int $numberOfRows): array { $highRow = $row + $numberOfRows - 1; $holdRowDimensions = []; foreach ($this->rowDimensions as $rowDimension) { $num = $rowDimension->getRowIndex(); if ($num < $row) { $holdRowDimensions[$num] = $rowDimension; } elseif ($num > $highRow) { $num -= $numberOfRows; $cloneDimension = clone $rowDimension; $cloneDimension->setRowIndex(/** @scrutinizer ignore-type */ $num); $holdRowDimensions[$num] = $cloneDimension; } } return $holdRowDimensions; } /** * Remove a column, updating all possible related data. * * @param string $column Remove columns starting with this column name, eg: 'A' * @param int $numberOfColumns Number of columns to remove * * @return $this */ public function removeColumn(string $column, int $numberOfColumns = 1) { if (is_numeric($column)) { throw new Exception('Column references should not be numeric.'); } $highestColumn = $this->getHighestDataColumn(); $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn); $pColumnIndex = Coordinate::columnIndexFromString($column); $holdColumnDimensions = $this->removeColumnDimensions($pColumnIndex, $numberOfColumns); $column = Coordinate::stringFromColumnIndex($pColumnIndex + $numberOfColumns); $objReferenceHelper = ReferenceHelper::getInstance(); $objReferenceHelper->insertNewBefore($column . '1', -$numberOfColumns, 0, $this); $this->columnDimensions = $holdColumnDimensions; if ($pColumnIndex > $highestColumnIndex) { return $this; } $maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1; for ($c = 0, $n = min($maxPossibleColumnsToBeRemoved, $numberOfColumns); $c < $n; ++$c) { $this->getCellCollection()->removeColumn($highestColumn); $highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1); } $this->garbageCollect(); return $this; } private function removeColumnDimensions(int $pColumnIndex, int $numberOfColumns): array { $highCol = $pColumnIndex + $numberOfColumns - 1; $holdColumnDimensions = []; foreach ($this->columnDimensions as $columnDimension) { $num = $columnDimension->getColumnNumeric(); if ($num < $pColumnIndex) { $str = $columnDimension->getColumnIndex(); $holdColumnDimensions[$str] = $columnDimension; } elseif ($num > $highCol) { $cloneDimension = clone $columnDimension; $cloneDimension->setColumnNumeric($num - $numberOfColumns); $str = $cloneDimension->getColumnIndex(); $holdColumnDimensions[$str] = $cloneDimension; } } return $holdColumnDimensions; } /** * Remove a column, updating all possible related data. * * @param int $columnIndex Remove starting with this column Index (numeric column coordinate) * @param int $numColumns Number of columns to remove * * @return $this */ public function removeColumnByIndex(int $columnIndex, int $numColumns = 1) { if ($columnIndex >= 1) { return $this->removeColumn(Coordinate::stringFromColumnIndex($columnIndex), $numColumns); } throw new Exception('Columns to be deleted should at least start from column A (1)'); } /** * Show gridlines? */ public function getShowGridlines(): bool { return $this->showGridlines; } /** * Set show gridlines. * * @param bool $showGridLines Show gridlines (true/false) * * @return $this */ public function setShowGridlines(bool $showGridLines): self { $this->showGridlines = $showGridLines; return $this; } /** * Print gridlines? */ public function getPrintGridlines(): bool { return $this->printGridlines; } /** * Set print gridlines. * * @param bool $printGridLines Print gridlines (true/false) * * @return $this */ public function setPrintGridlines(bool $printGridLines): self { $this->printGridlines = $printGridLines; return $this; } /** * Show row and column headers? */ public function getShowRowColHeaders(): bool { return $this->showRowColHeaders; } /** * Set show row and column headers. * * @param bool $showRowColHeaders Show row and column headers (true/false) * * @return $this */ public function setShowRowColHeaders(bool $showRowColHeaders): self { $this->showRowColHeaders = $showRowColHeaders; return $this; } /** * Show summary below? (Row/Column outlining). */ public function getShowSummaryBelow(): bool { return $this->showSummaryBelow; } /** * Set show summary below. * * @param bool $showSummaryBelow Show summary below (true/false) * * @return $this */ public function setShowSummaryBelow(bool $showSummaryBelow): self { $this->showSummaryBelow = $showSummaryBelow; return $this; } /** * Show summary right? (Row/Column outlining). */ public function getShowSummaryRight(): bool { return $this->showSummaryRight; } /** * Set show summary right. * * @param bool $showSummaryRight Show summary right (true/false) * * @return $this */ public function setShowSummaryRight(bool $showSummaryRight): self { $this->showSummaryRight = $showSummaryRight; return $this; } /** * Get comments. * * @return Comment[] */ public function getComments() { return $this->comments; } /** * Set comments array for the entire sheet. * * @param Comment[] $comments * * @return $this */ public function setComments(array $comments): self { $this->comments = $comments; return $this; } /** * Remove comment from cell. * * @param array|CellAddress|string $cellCoordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * * @return $this */ public function removeComment($cellCoordinate): self { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate)); if (Coordinate::coordinateIsRange($cellAddress)) { throw new Exception('Cell coordinate string can not be a range of cells.'); } elseif (strpos($cellAddress, '$') !== false) { throw new Exception('Cell coordinate string must not be absolute.'); } elseif ($cellAddress == '') { throw new Exception('Cell coordinate can not be zero-length string.'); } // Check if we have a comment for this cell and delete it if (isset($this->comments[$cellAddress])) { unset($this->comments[$cellAddress]); } return $this; } /** * Get comment for cell. * * @param array|CellAddress|string $cellCoordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. */ public function getComment($cellCoordinate): Comment { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate)); if (Coordinate::coordinateIsRange($cellAddress)) { throw new Exception('Cell coordinate string can not be a range of cells.'); } elseif (strpos($cellAddress, '$') !== false) { throw new Exception('Cell coordinate string must not be absolute.'); } elseif ($cellAddress == '') { throw new Exception('Cell coordinate can not be zero-length string.'); } // Check if we already have a comment for this cell. if (isset($this->comments[$cellAddress])) { return $this->comments[$cellAddress]; } // If not, create a new comment. $newComment = new Comment(); $this->comments[$cellAddress] = $newComment; return $newComment; } /** * Get comment for cell by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the getComment() method with a cell address such as 'C5' instead;, * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @see Worksheet::getComment() * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell */ public function getCommentByColumnAndRow($columnIndex, $row): Comment { return $this->getComment(Coordinate::stringFromColumnIndex($columnIndex) . $row); } /** * Get active cell. * * @return string Example: 'A1' */ public function getActiveCell() { return $this->activeCell; } /** * Get selected cells. * * @return string */ public function getSelectedCells() { return $this->selectedCells; } /** * Selected cell. * * @param string $coordinate Cell (i.e. A1) * * @return $this */ public function setSelectedCell($coordinate) { return $this->setSelectedCells($coordinate); } /** * Select a range of cells. * * @param AddressRange|array|CellAddress|int|string $coordinate A simple string containing a Cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or a CellAddress or AddressRange object. * * @return $this */ public function setSelectedCells($coordinate) { if (is_string($coordinate)) { $coordinate = Validations::definedNameToCoordinate($coordinate, $this); } $coordinate = Validations::validateCellOrCellRange($coordinate); if (Coordinate::coordinateIsRange($coordinate)) { [$first] = Coordinate::splitRange($coordinate); $this->activeCell = $first[0]; } else { $this->activeCell = $coordinate; } $this->selectedCells = $coordinate; return $this; } /** * Selected cell by using numeric cell coordinates. * * @deprecated 1.23.0 * Use the setSelectedCells() method with a cell address such as 'C5' instead;, * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @see Worksheet::setSelectedCells() * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * * @return $this */ public function setSelectedCellByColumnAndRow($columnIndex, $row) { return $this->setSelectedCells(Coordinate::stringFromColumnIndex($columnIndex) . $row); } /** * Get right-to-left. * * @return bool */ public function getRightToLeft() { return $this->rightToLeft; } /** * Set right-to-left. * * @param bool $value Right-to-left true/false * * @return $this */ public function setRightToLeft($value) { $this->rightToLeft = $value; return $this; } /** * Fill worksheet from values in array. * * @param array $source Source array * @param mixed $nullValue Value in source array that stands for blank cell * @param string $startCell Insert array starting from this cell address as the top left coordinate * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array * * @return $this */ public function fromArray(array $source, $nullValue = null, $startCell = 'A1', $strictNullComparison = false) { // Convert a 1-D array to 2-D (for ease of looping) if (!is_array(end($source))) { $source = [$source]; } // start coordinate [$startColumn, $startRow] = Coordinate::coordinateFromString($startCell); // Loop through $source foreach ($source as $rowData) { $currentColumn = $startColumn; foreach ($rowData as $cellValue) { if ($strictNullComparison) { if ($cellValue !== $nullValue) { // Set cell value $this->getCell($currentColumn . $startRow)->setValue($cellValue); } } else { if ($cellValue != $nullValue) { // Set cell value $this->getCell($currentColumn . $startRow)->setValue($cellValue); } } ++$currentColumn; } ++$startRow; } return $this; } /** * @param mixed $nullValue * * @throws Exception * @throws \PhpOffice\PhpSpreadsheet\Calculation\Exception * * @return mixed */ protected function cellToArray(Cell $cell, bool $calculateFormulas, bool $formatData, $nullValue) { $returnValue = $nullValue; if ($cell->getValue() !== null) { if ($cell->getValue() instanceof RichText) { $returnValue = $cell->getValue()->getPlainText(); } else { $returnValue = ($calculateFormulas) ? $cell->getCalculatedValue() : $cell->getValue(); } if ($formatData) { $style = $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex()); $returnValue = NumberFormat::toFormattedString( $returnValue, $style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL ); } } return $returnValue; } /** * Create array from a range of cells. * * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param bool $calculateFormulas Should formulas be calculated? * @param bool $formatData Should formatting be applied to cell values? * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. */ public function rangeToArray( string $range, $nullValue = null, bool $calculateFormulas = true, bool $formatData = true, bool $returnCellRef = false, bool $ignoreHidden = false ): array { $range = Validations::validateCellOrCellRange($range); $returnValue = []; // Identify the range that we need to extract from the worksheet [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range); $minCol = Coordinate::stringFromColumnIndex($rangeStart[0]); $minRow = $rangeStart[1]; $maxCol = Coordinate::stringFromColumnIndex($rangeEnd[0]); $maxRow = $rangeEnd[1]; ++$maxCol; // Loop through rows $r = -1; for ($row = $minRow; $row <= $maxRow; ++$row) { if (($ignoreHidden === true) && ($this->getRowDimension($row)->getVisible() === false)) { continue; } $rowRef = $returnCellRef ? $row : ++$r; $c = -1; // Loop through columns in the current row for ($col = $minCol; $col !== $maxCol; ++$col) { if (($ignoreHidden === true) && ($this->getColumnDimension($col)->getVisible() === false)) { continue; } $columnRef = $returnCellRef ? $col : ++$c; // Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen // so we test and retrieve directly against cellCollection $cell = $this->cellCollection->get("{$col}{$row}"); $returnValue[$rowRef][$columnRef] = $nullValue; if ($cell !== null) { $returnValue[$rowRef][$columnRef] = $this->cellToArray($cell, $calculateFormulas, $formatData, $nullValue); } } } // Return return $returnValue; } private function validateNamedRange(string $definedName, bool $returnNullIfInvalid = false): ?DefinedName { $namedRange = DefinedName::resolveName($definedName, $this); if ($namedRange === null) { if ($returnNullIfInvalid) { return null; } throw new Exception('Named Range ' . $definedName . ' does not exist.'); } if ($namedRange->isFormula()) { if ($returnNullIfInvalid) { return null; } throw new Exception('Defined Named ' . $definedName . ' is a formula, not a range or cell.'); } if ($namedRange->getLocalOnly()) { $worksheet = $namedRange->getWorksheet(); if ($worksheet === null || $this->getHashCode() !== $worksheet->getHashCode()) { if ($returnNullIfInvalid) { return null; } throw new Exception( 'Named range ' . $definedName . ' is not accessible from within sheet ' . $this->getTitle() ); } } return $namedRange; } /** * Create array from a range of cells. * * @param string $definedName The Named Range that should be returned * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param bool $calculateFormulas Should formulas be calculated? * @param bool $formatData Should formatting be applied to cell values? * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. */ public function namedRangeToArray( string $definedName, $nullValue = null, bool $calculateFormulas = true, bool $formatData = true, bool $returnCellRef = false, bool $ignoreHidden = false ): array { $retVal = []; $namedRange = $this->validateNamedRange($definedName); if ($namedRange !== null) { $cellRange = ltrim(substr($namedRange->getValue(), (int) strrpos($namedRange->getValue(), '!')), '!'); $cellRange = str_replace('$', '', $cellRange); $workSheet = $namedRange->getWorksheet(); if ($workSheet !== null) { $retVal = $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden); } } return $retVal; } /** * Create array from worksheet. * * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param bool $calculateFormulas Should formulas be calculated? * @param bool $formatData Should formatting be applied to cell values? * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. */ public function toArray( $nullValue = null, bool $calculateFormulas = true, bool $formatData = true, bool $returnCellRef = false, bool $ignoreHidden = false ): array { // Garbage collect... $this->garbageCollect(); // Identify the range that we need to extract from the worksheet $maxCol = $this->getHighestColumn(); $maxRow = $this->getHighestRow(); // Return return $this->rangeToArray("A1:{$maxCol}{$maxRow}", $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden); } /** * Get row iterator. * * @param int $startRow The row number at which to start iterating * @param int $endRow The row number at which to stop iterating * * @return RowIterator */ public function getRowIterator($startRow = 1, $endRow = null) { return new RowIterator($this, $startRow, $endRow); } /** * Get column iterator. * * @param string $startColumn The column address at which to start iterating * @param string $endColumn The column address at which to stop iterating * * @return ColumnIterator */ public function getColumnIterator($startColumn = 'A', $endColumn = null) { return new ColumnIterator($this, $startColumn, $endColumn); } /** * Run PhpSpreadsheet garbage collector. * * @return $this */ public function garbageCollect() { // Flush cache $this->cellCollection->get('A1'); // Lookup highest column and highest row if cells are cleaned $colRow = $this->cellCollection->getHighestRowAndColumn(); $highestRow = $colRow['row']; $highestColumn = Coordinate::columnIndexFromString($colRow['column']); // Loop through column dimensions foreach ($this->columnDimensions as $dimension) { $highestColumn = max($highestColumn, Coordinate::columnIndexFromString($dimension->getColumnIndex())); } // Loop through row dimensions foreach ($this->rowDimensions as $dimension) { $highestRow = max($highestRow, $dimension->getRowIndex()); } // Cache values if ($highestColumn < 1) { $this->cachedHighestColumn = 1; } else { $this->cachedHighestColumn = $highestColumn; } $this->cachedHighestRow = $highestRow; // Return return $this; } /** * Get hash code. * * @return string Hash code */ public function getHashCode() { if ($this->dirty) { $this->hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' : 'f') . __CLASS__); $this->dirty = false; } return $this->hash; } /** * Extract worksheet title from range. * * Example: extractSheetTitle("testSheet!A1") ==> 'A1' * Example: extractSheetTitle("testSheet!A1:C3") ==> 'A1:C3' * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> ['testSheet 1', 'A1']; * Example: extractSheetTitle("'testSheet 1'!A1:C3", true) ==> ['testSheet 1', 'A1:C3']; * Example: extractSheetTitle("A1", true) ==> ['', 'A1']; * Example: extractSheetTitle("A1:C3", true) ==> ['', 'A1:C3'] * * @param string $range Range to extract title from * @param bool $returnRange Return range? (see example) * * @return mixed */ public static function extractSheetTitle($range, $returnRange = false) { if (empty($range)) { return $returnRange ? [null, null] : null; } // Sheet title included? if (($sep = strrpos($range, '!')) === false) { return $returnRange ? ['', $range] : ''; } if ($returnRange) { return [substr($range, 0, $sep), substr($range, $sep + 1)]; } return substr($range, $sep + 1); } /** * Get hyperlink. * * @param string $cellCoordinate Cell coordinate to get hyperlink for, eg: 'A1' * * @return Hyperlink */ public function getHyperlink($cellCoordinate) { // return hyperlink if we already have one if (isset($this->hyperlinkCollection[$cellCoordinate])) { return $this->hyperlinkCollection[$cellCoordinate]; } // else create hyperlink $this->hyperlinkCollection[$cellCoordinate] = new Hyperlink(); return $this->hyperlinkCollection[$cellCoordinate]; } /** * Set hyperlink. * * @param string $cellCoordinate Cell coordinate to insert hyperlink, eg: 'A1' * * @return $this */ public function setHyperlink($cellCoordinate, ?Hyperlink $hyperlink = null) { if ($hyperlink === null) { unset($this->hyperlinkCollection[$cellCoordinate]); } else { $this->hyperlinkCollection[$cellCoordinate] = $hyperlink; } return $this; } /** * Hyperlink at a specific coordinate exists? * * @param string $coordinate eg: 'A1' * * @return bool */ public function hyperlinkExists($coordinate) { return isset($this->hyperlinkCollection[$coordinate]); } /** * Get collection of hyperlinks. * * @return Hyperlink[] */ public function getHyperlinkCollection() { return $this->hyperlinkCollection; } /** * Get data validation. * * @param string $cellCoordinate Cell coordinate to get data validation for, eg: 'A1' * * @return DataValidation */ public function getDataValidation($cellCoordinate) { // return data validation if we already have one if (isset($this->dataValidationCollection[$cellCoordinate])) { return $this->dataValidationCollection[$cellCoordinate]; } // else create data validation $this->dataValidationCollection[$cellCoordinate] = new DataValidation(); return $this->dataValidationCollection[$cellCoordinate]; } /** * Set data validation. * * @param string $cellCoordinate Cell coordinate to insert data validation, eg: 'A1' * * @return $this */ public function setDataValidation($cellCoordinate, ?DataValidation $dataValidation = null) { if ($dataValidation === null) { unset($this->dataValidationCollection[$cellCoordinate]); } else { $this->dataValidationCollection[$cellCoordinate] = $dataValidation; } return $this; } /** * Data validation at a specific coordinate exists? * * @param string $coordinate eg: 'A1' * * @return bool */ public function dataValidationExists($coordinate) { return isset($this->dataValidationCollection[$coordinate]); } /** * Get collection of data validations. * * @return DataValidation[] */ public function getDataValidationCollection() { return $this->dataValidationCollection; } /** * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet. * * @param string $range * * @return string Adjusted range value */ public function shrinkRangeToFit($range) { $maxCol = $this->getHighestColumn(); $maxRow = $this->getHighestRow(); $maxCol = Coordinate::columnIndexFromString($maxCol); $rangeBlocks = explode(' ', $range); foreach ($rangeBlocks as &$rangeSet) { $rangeBoundaries = Coordinate::getRangeBoundaries($rangeSet); if (Coordinate::columnIndexFromString($rangeBoundaries[0][0]) > $maxCol) { $rangeBoundaries[0][0] = Coordinate::stringFromColumnIndex($maxCol); } if ($rangeBoundaries[0][1] > $maxRow) { $rangeBoundaries[0][1] = $maxRow; } if (Coordinate::columnIndexFromString($rangeBoundaries[1][0]) > $maxCol) { $rangeBoundaries[1][0] = Coordinate::stringFromColumnIndex($maxCol); } if ($rangeBoundaries[1][1] > $maxRow) { $rangeBoundaries[1][1] = $maxRow; } $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1]; } unset($rangeSet); return implode(' ', $rangeBlocks); } /** * Get tab color. * * @return Color */ public function getTabColor() { if ($this->tabColor === null) { $this->tabColor = new Color(); } return $this->tabColor; } /** * Reset tab color. * * @return $this */ public function resetTabColor() { $this->tabColor = null; return $this; } /** * Tab color set? * * @return bool */ public function isTabColorSet() { return $this->tabColor !== null; } /** * Copy worksheet (!= clone!). * * @return static */ public function copy() { return clone $this; } /** * Returns a boolean true if the specified row contains no cells. By default, this means that no cell records * exist in the collection for this row. false will be returned otherwise. * This rule can be modified by passing a $definitionOfEmptyFlags value: * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value * cells, then the row will be considered empty. * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty * string value cells, then the row will be considered empty. * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * If the only cells in the collection are null value or empty string value cells, then the row * will be considered empty. * * @param int $definitionOfEmptyFlags * Possible Flag Values are: * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL */ public function isEmptyRow(int $rowId, int $definitionOfEmptyFlags = 0): bool { try { $iterator = new RowIterator($this, $rowId, $rowId); $iterator->seek($rowId); $row = $iterator->current(); } catch (Exception $e) { return true; } return $row->isEmpty($definitionOfEmptyFlags); } /** * Returns a boolean true if the specified column contains no cells. By default, this means that no cell records * exist in the collection for this column. false will be returned otherwise. * This rule can be modified by passing a $definitionOfEmptyFlags value: * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value * cells, then the column will be considered empty. * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty * string value cells, then the column will be considered empty. * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * If the only cells in the collection are null value or empty string value cells, then the column * will be considered empty. * * @param int $definitionOfEmptyFlags * Possible Flag Values are: * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL */ public function isEmptyColumn(string $columnId, int $definitionOfEmptyFlags = 0): bool { try { $iterator = new ColumnIterator($this, $columnId, $columnId); $iterator->seek($columnId); $column = $iterator->current(); } catch (Exception $e) { return true; } return $column->isEmpty($definitionOfEmptyFlags); } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { // @phpstan-ignore-next-line foreach ($this as $key => $val) { if ($key == 'parent') { continue; } if (is_object($val) || (is_array($val))) { if ($key == 'cellCollection') { $newCollection = $this->cellCollection->cloneCellCollection($this); $this->cellCollection = $newCollection; } elseif ($key == 'drawingCollection') { $currentCollection = $this->drawingCollection; $this->drawingCollection = new ArrayObject(); foreach ($currentCollection as $item) { if (is_object($item)) { $newDrawing = clone $item; $newDrawing->setWorksheet($this); } } } elseif (($key == 'autoFilter') && ($this->autoFilter instanceof AutoFilter)) { $newAutoFilter = clone $this->autoFilter; $this->autoFilter = $newAutoFilter; $this->autoFilter->setParent($this); } else { $this->{$key} = unserialize(serialize($val)); } } } } /** * Define the code name of the sheet. * * @param string $codeName Same rule as Title minus space not allowed (but, like Excel, change * silently space to underscore) * @param bool $validate False to skip validation of new title. WARNING: This should only be set * at parse time (by Readers), where titles can be assumed to be valid. * * @return $this */ public function setCodeName($codeName, $validate = true) { // Is this a 'rename' or not? if ($this->getCodeName() == $codeName) { return $this; } if ($validate) { $codeName = str_replace(' ', '_', $codeName); //Excel does this automatically without flinching, we are doing the same // Syntax check // throw an exception if not valid self::checkSheetCodeName($codeName); // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_' if ($this->parent !== null) { // Is there already such sheet name? if ($this->parent->sheetCodeNameExists($codeName)) { // Use name, but append with lowest possible integer if (Shared\StringHelper::countCharacters($codeName) > 29) { $codeName = Shared\StringHelper::substring($codeName, 0, 29); } $i = 1; while ($this->getParentOrThrow()->sheetCodeNameExists($codeName . '_' . $i)) { ++$i; if ($i == 10) { if (Shared\StringHelper::countCharacters($codeName) > 28) { $codeName = Shared\StringHelper::substring($codeName, 0, 28); } } elseif ($i == 100) { if (Shared\StringHelper::countCharacters($codeName) > 27) { $codeName = Shared\StringHelper::substring($codeName, 0, 27); } } } $codeName .= '_' . $i; // ok, we have a valid name } } } $this->codeName = $codeName; return $this; } /** * Return the code name of the sheet. * * @return null|string */ public function getCodeName() { return $this->codeName; } /** * Sheet has a code name ? * * @return bool */ public function hasCodeName() { return $this->codeName !== null; } public static function nameRequiresQuotes(string $sheetName): bool { return preg_match(self::SHEET_NAME_REQUIRES_NO_QUOTES, $sheetName) !== 1; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFit.php000064400000003267152427537250017354 0ustar00worksheet = $worksheet; } public function getAutoFilterIndentRanges(): array { $autoFilterIndentRanges = []; $autoFilterIndentRanges[] = $this->getAutoFilterIndentRange($this->worksheet->getAutoFilter()); foreach ($this->worksheet->getTableCollection() as $table) { /** @var Table $table */ if ($table->getShowHeaderRow() === true && $table->getAllowFilter() === true) { $autoFilter = $table->getAutoFilter(); if ($autoFilter !== null) { $autoFilterIndentRanges[] = $this->getAutoFilterIndentRange($autoFilter); } } } return array_filter($autoFilterIndentRanges); } private function getAutoFilterIndentRange(AutoFilter $autoFilter): ?string { $autoFilterRange = $autoFilter->getRange(); $autoFilterIndentRange = null; if (!empty($autoFilterRange)) { $autoFilterRangeBoundaries = Coordinate::rangeBoundaries($autoFilterRange); $autoFilterIndentRange = (string) new CellRange( CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[0][0], $autoFilterRangeBoundaries[0][1]), CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[1][0], $autoFilterRangeBoundaries[0][1]) ); } return $autoFilterIndentRange; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php000064400000002361152427537250017564 0ustar00 */ class Iterator implements \Iterator { /** * Spreadsheet to iterate. * * @var Spreadsheet */ private $subject; /** * Current iterator position. * * @var int */ private $position = 0; /** * Create a new worksheet iterator. */ public function __construct(Spreadsheet $subject) { // Set subject $this->subject = $subject; } /** * Rewind iterator. */ public function rewind(): void { $this->position = 0; } /** * Current Worksheet. */ public function current(): Worksheet { return $this->subject->getSheet($this->position); } /** * Current key. */ public function key(): int { return $this->position; } /** * Next value. */ public function next(): void { ++$this->position; } /** * Are there more Worksheet instances available? */ public function valid(): bool { return $this->position < $this->subject->getSheetCount() && $this->position >= 0; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php000064400000006341152427537250020172 0ustar00left; } /** * Set Left. * * @param float $left * * @return $this */ public function setLeft($left) { $this->left = $left; return $this; } /** * Get Right. * * @return float */ public function getRight() { return $this->right; } /** * Set Right. * * @param float $right * * @return $this */ public function setRight($right) { $this->right = $right; return $this; } /** * Get Top. * * @return float */ public function getTop() { return $this->top; } /** * Set Top. * * @param float $top * * @return $this */ public function setTop($top) { $this->top = $top; return $this; } /** * Get Bottom. * * @return float */ public function getBottom() { return $this->bottom; } /** * Set Bottom. * * @param float $bottom * * @return $this */ public function setBottom($bottom) { $this->bottom = $bottom; return $this; } /** * Get Header. * * @return float */ public function getHeader() { return $this->header; } /** * Set Header. * * @param float $header * * @return $this */ public function setHeader($header) { $this->header = $header; return $this; } /** * Get Footer. * * @return float */ public function getFooter() { return $this->footer; } /** * Set Footer. * * @param float $footer * * @return $this */ public function setFooter($footer) { $this->footer = $footer; return $this; } public static function fromCentimeters(float $value): float { return $value / 2.54; } public static function toCentimeters(float $value): float { return $value * 2.54; } public static function fromMillimeters(float $value): float { return $value / 25.4; } public static function toMillimeters(float $value): float { return $value * 25.4; } public static function fromPoints(float $value): float { return $value / 72; } public static function toPoints(float $value): float { return $value * 72; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php000064400000065233152427537250017677 0ustar00 * Paper size taken from Office Open XML Part 4 - Markup Language Reference, page 1988:. * * 1 = Letter paper (8.5 in. by 11 in.) * 2 = Letter small paper (8.5 in. by 11 in.) * 3 = Tabloid paper (11 in. by 17 in.) * 4 = Ledger paper (17 in. by 11 in.) * 5 = Legal paper (8.5 in. by 14 in.) * 6 = Statement paper (5.5 in. by 8.5 in.) * 7 = Executive paper (7.25 in. by 10.5 in.) * 8 = A3 paper (297 mm by 420 mm) * 9 = A4 paper (210 mm by 297 mm) * 10 = A4 small paper (210 mm by 297 mm) * 11 = A5 paper (148 mm by 210 mm) * 12 = B4 paper (250 mm by 353 mm) * 13 = B5 paper (176 mm by 250 mm) * 14 = Folio paper (8.5 in. by 13 in.) * 15 = Quarto paper (215 mm by 275 mm) * 16 = Standard paper (10 in. by 14 in.) * 17 = Standard paper (11 in. by 17 in.) * 18 = Note paper (8.5 in. by 11 in.) * 19 = #9 envelope (3.875 in. by 8.875 in.) * 20 = #10 envelope (4.125 in. by 9.5 in.) * 21 = #11 envelope (4.5 in. by 10.375 in.) * 22 = #12 envelope (4.75 in. by 11 in.) * 23 = #14 envelope (5 in. by 11.5 in.) * 24 = C paper (17 in. by 22 in.) * 25 = D paper (22 in. by 34 in.) * 26 = E paper (34 in. by 44 in.) * 27 = DL envelope (110 mm by 220 mm) * 28 = C5 envelope (162 mm by 229 mm) * 29 = C3 envelope (324 mm by 458 mm) * 30 = C4 envelope (229 mm by 324 mm) * 31 = C6 envelope (114 mm by 162 mm) * 32 = C65 envelope (114 mm by 229 mm) * 33 = B4 envelope (250 mm by 353 mm) * 34 = B5 envelope (176 mm by 250 mm) * 35 = B6 envelope (176 mm by 125 mm) * 36 = Italy envelope (110 mm by 230 mm) * 37 = Monarch envelope (3.875 in. by 7.5 in.). * 38 = 6 3/4 envelope (3.625 in. by 6.5 in.) * 39 = US standard fanfold (14.875 in. by 11 in.) * 40 = German standard fanfold (8.5 in. by 12 in.) * 41 = German legal fanfold (8.5 in. by 13 in.) * 42 = ISO B4 (250 mm by 353 mm) * 43 = Japanese double postcard (200 mm by 148 mm) * 44 = Standard paper (9 in. by 11 in.) * 45 = Standard paper (10 in. by 11 in.) * 46 = Standard paper (15 in. by 11 in.) * 47 = Invite envelope (220 mm by 220 mm) * 50 = Letter extra paper (9.275 in. by 12 in.) * 51 = Legal extra paper (9.275 in. by 15 in.) * 52 = Tabloid extra paper (11.69 in. by 18 in.) * 53 = A4 extra paper (236 mm by 322 mm) * 54 = Letter transverse paper (8.275 in. by 11 in.) * 55 = A4 transverse paper (210 mm by 297 mm) * 56 = Letter extra transverse paper (9.275 in. by 12 in.) * 57 = SuperA/SuperA/A4 paper (227 mm by 356 mm) * 58 = SuperB/SuperB/A3 paper (305 mm by 487 mm) * 59 = Letter plus paper (8.5 in. by 12.69 in.) * 60 = A4 plus paper (210 mm by 330 mm) * 61 = A5 transverse paper (148 mm by 210 mm) * 62 = JIS B5 transverse paper (182 mm by 257 mm) * 63 = A3 extra paper (322 mm by 445 mm) * 64 = A5 extra paper (174 mm by 235 mm) * 65 = ISO B5 extra paper (201 mm by 276 mm) * 66 = A2 paper (420 mm by 594 mm) * 67 = A3 transverse paper (297 mm by 420 mm) * 68 = A3 extra transverse paper (322 mm by 445 mm) * */ class PageSetup { // Paper size const PAPERSIZE_LETTER = 1; const PAPERSIZE_LETTER_SMALL = 2; const PAPERSIZE_TABLOID = 3; const PAPERSIZE_LEDGER = 4; const PAPERSIZE_LEGAL = 5; const PAPERSIZE_STATEMENT = 6; const PAPERSIZE_EXECUTIVE = 7; const PAPERSIZE_A3 = 8; const PAPERSIZE_A4 = 9; const PAPERSIZE_A4_SMALL = 10; const PAPERSIZE_A5 = 11; const PAPERSIZE_B4 = 12; const PAPERSIZE_B5 = 13; const PAPERSIZE_FOLIO = 14; const PAPERSIZE_QUARTO = 15; const PAPERSIZE_STANDARD_1 = 16; const PAPERSIZE_STANDARD_2 = 17; const PAPERSIZE_NOTE = 18; const PAPERSIZE_NO9_ENVELOPE = 19; const PAPERSIZE_NO10_ENVELOPE = 20; const PAPERSIZE_NO11_ENVELOPE = 21; const PAPERSIZE_NO12_ENVELOPE = 22; const PAPERSIZE_NO14_ENVELOPE = 23; const PAPERSIZE_C = 24; const PAPERSIZE_D = 25; const PAPERSIZE_E = 26; const PAPERSIZE_DL_ENVELOPE = 27; const PAPERSIZE_C5_ENVELOPE = 28; const PAPERSIZE_C3_ENVELOPE = 29; const PAPERSIZE_C4_ENVELOPE = 30; const PAPERSIZE_C6_ENVELOPE = 31; const PAPERSIZE_C65_ENVELOPE = 32; const PAPERSIZE_B4_ENVELOPE = 33; const PAPERSIZE_B5_ENVELOPE = 34; const PAPERSIZE_B6_ENVELOPE = 35; const PAPERSIZE_ITALY_ENVELOPE = 36; const PAPERSIZE_MONARCH_ENVELOPE = 37; const PAPERSIZE_6_3_4_ENVELOPE = 38; const PAPERSIZE_US_STANDARD_FANFOLD = 39; const PAPERSIZE_GERMAN_STANDARD_FANFOLD = 40; const PAPERSIZE_GERMAN_LEGAL_FANFOLD = 41; const PAPERSIZE_ISO_B4 = 42; const PAPERSIZE_JAPANESE_DOUBLE_POSTCARD = 43; const PAPERSIZE_STANDARD_PAPER_1 = 44; const PAPERSIZE_STANDARD_PAPER_2 = 45; const PAPERSIZE_STANDARD_PAPER_3 = 46; const PAPERSIZE_INVITE_ENVELOPE = 47; const PAPERSIZE_LETTER_EXTRA_PAPER = 48; const PAPERSIZE_LEGAL_EXTRA_PAPER = 49; const PAPERSIZE_TABLOID_EXTRA_PAPER = 50; const PAPERSIZE_A4_EXTRA_PAPER = 51; const PAPERSIZE_LETTER_TRANSVERSE_PAPER = 52; const PAPERSIZE_A4_TRANSVERSE_PAPER = 53; const PAPERSIZE_LETTER_EXTRA_TRANSVERSE_PAPER = 54; const PAPERSIZE_SUPERA_SUPERA_A4_PAPER = 55; const PAPERSIZE_SUPERB_SUPERB_A3_PAPER = 56; const PAPERSIZE_LETTER_PLUS_PAPER = 57; const PAPERSIZE_A4_PLUS_PAPER = 58; const PAPERSIZE_A5_TRANSVERSE_PAPER = 59; const PAPERSIZE_JIS_B5_TRANSVERSE_PAPER = 60; const PAPERSIZE_A3_EXTRA_PAPER = 61; const PAPERSIZE_A5_EXTRA_PAPER = 62; const PAPERSIZE_ISO_B5_EXTRA_PAPER = 63; const PAPERSIZE_A2_PAPER = 64; const PAPERSIZE_A3_TRANSVERSE_PAPER = 65; const PAPERSIZE_A3_EXTRA_TRANSVERSE_PAPER = 66; // Page orientation const ORIENTATION_DEFAULT = 'default'; const ORIENTATION_LANDSCAPE = 'landscape'; const ORIENTATION_PORTRAIT = 'portrait'; // Print Range Set Method const SETPRINTRANGE_OVERWRITE = 'O'; const SETPRINTRANGE_INSERT = 'I'; const PAGEORDER_OVER_THEN_DOWN = 'overThenDown'; const PAGEORDER_DOWN_THEN_OVER = 'downThenOver'; /** * Paper size default. * * @var int */ private static $paperSizeDefault = self::PAPERSIZE_LETTER; /** * Paper size. * * @var ?int */ private $paperSize; /** * Orientation default. * * @var string */ private static $orientationDefault = self::ORIENTATION_DEFAULT; /** * Orientation. * * @var string */ private $orientation; /** * Scale (Print Scale). * * Print scaling. Valid values range from 10 to 400 * This setting is overridden when fitToWidth and/or fitToHeight are in use * * @var null|int */ private $scale = 100; /** * Fit To Page * Whether scale or fitToWith / fitToHeight applies. * * @var bool */ private $fitToPage = false; /** * Fit To Height * Number of vertical pages to fit on. * * @var null|int */ private $fitToHeight = 1; /** * Fit To Width * Number of horizontal pages to fit on. * * @var null|int */ private $fitToWidth = 1; /** * Columns to repeat at left. * * @var array Containing start column and end column, empty array if option unset */ private $columnsToRepeatAtLeft = ['', '']; /** * Rows to repeat at top. * * @var array Containing start row number and end row number, empty array if option unset */ private $rowsToRepeatAtTop = [0, 0]; /** * Center page horizontally. * * @var bool */ private $horizontalCentered = false; /** * Center page vertically. * * @var bool */ private $verticalCentered = false; /** * Print area. * * @var null|string */ private $printArea; /** * First page number. * * @var ?int */ private $firstPageNumber; /** @var string */ private $pageOrder = self::PAGEORDER_DOWN_THEN_OVER; /** * Create a new PageSetup. */ public function __construct() { $this->orientation = self::$orientationDefault; } /** * Get Paper Size. * * @return int */ public function getPaperSize() { return $this->paperSize ?? self::$paperSizeDefault; } /** * Set Paper Size. * * @param int $paperSize see self::PAPERSIZE_* * * @return $this */ public function setPaperSize($paperSize) { $this->paperSize = $paperSize; return $this; } /** * Get Paper Size default. */ public static function getPaperSizeDefault(): int { return self::$paperSizeDefault; } /** * Set Paper Size Default. */ public static function setPaperSizeDefault(int $paperSize): void { self::$paperSizeDefault = $paperSize; } /** * Get Orientation. * * @return string */ public function getOrientation() { return $this->orientation; } /** * Set Orientation. * * @param string $orientation see self::ORIENTATION_* * * @return $this */ public function setOrientation($orientation) { if ($orientation === self::ORIENTATION_LANDSCAPE || $orientation === self::ORIENTATION_PORTRAIT || $orientation === self::ORIENTATION_DEFAULT) { $this->orientation = $orientation; } return $this; } public static function getOrientationDefault(): string { return self::$orientationDefault; } public static function setOrientationDefault(string $orientation): void { if ($orientation === self::ORIENTATION_LANDSCAPE || $orientation === self::ORIENTATION_PORTRAIT || $orientation === self::ORIENTATION_DEFAULT) { self::$orientationDefault = $orientation; } } /** * Get Scale. * * @return null|int */ public function getScale() { return $this->scale; } /** * Set Scale. * Print scaling. Valid values range from 10 to 400 * This setting is overridden when fitToWidth and/or fitToHeight are in use. * * @param null|int $scale * @param bool $update Update fitToPage so scaling applies rather than fitToHeight / fitToWidth * * @return $this */ public function setScale($scale, $update = true) { // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface, // but it is apparently still able to handle any scale >= 0, where 0 results in 100 if ($scale === null || $scale >= 0) { $this->scale = $scale; if ($update) { $this->fitToPage = false; } } else { throw new PhpSpreadsheetException('Scale must not be negative'); } return $this; } /** * Get Fit To Page. * * @return bool */ public function getFitToPage() { return $this->fitToPage; } /** * Set Fit To Page. * * @param bool $fitToPage * * @return $this */ public function setFitToPage($fitToPage) { $this->fitToPage = $fitToPage; return $this; } /** * Get Fit To Height. * * @return null|int */ public function getFitToHeight() { return $this->fitToHeight; } /** * Set Fit To Height. * * @param null|int $fitToHeight * @param bool $update Update fitToPage so it applies rather than scaling * * @return $this */ public function setFitToHeight($fitToHeight, $update = true) { $this->fitToHeight = $fitToHeight; if ($update) { $this->fitToPage = true; } return $this; } /** * Get Fit To Width. * * @return null|int */ public function getFitToWidth() { return $this->fitToWidth; } /** * Set Fit To Width. * * @param null|int $value * @param bool $update Update fitToPage so it applies rather than scaling * * @return $this */ public function setFitToWidth($value, $update = true) { $this->fitToWidth = $value; if ($update) { $this->fitToPage = true; } return $this; } /** * Is Columns to repeat at left set? * * @return bool */ public function isColumnsToRepeatAtLeftSet() { if (!empty($this->columnsToRepeatAtLeft)) { if ($this->columnsToRepeatAtLeft[0] != '' && $this->columnsToRepeatAtLeft[1] != '') { return true; } } return false; } /** * Get Columns to repeat at left. * * @return array Containing start column and end column, empty array if option unset */ public function getColumnsToRepeatAtLeft() { return $this->columnsToRepeatAtLeft; } /** * Set Columns to repeat at left. * * @param array $columnsToRepeatAtLeft Containing start column and end column, empty array if option unset * * @return $this */ public function setColumnsToRepeatAtLeft(array $columnsToRepeatAtLeft) { $this->columnsToRepeatAtLeft = $columnsToRepeatAtLeft; return $this; } /** * Set Columns to repeat at left by start and end. * * @param string $start eg: 'A' * @param string $end eg: 'B' * * @return $this */ public function setColumnsToRepeatAtLeftByStartAndEnd($start, $end) { $this->columnsToRepeatAtLeft = [$start, $end]; return $this; } /** * Is Rows to repeat at top set? * * @return bool */ public function isRowsToRepeatAtTopSet() { if (!empty($this->rowsToRepeatAtTop)) { if ($this->rowsToRepeatAtTop[0] != 0 && $this->rowsToRepeatAtTop[1] != 0) { return true; } } return false; } /** * Get Rows to repeat at top. * * @return array Containing start column and end column, empty array if option unset */ public function getRowsToRepeatAtTop() { return $this->rowsToRepeatAtTop; } /** * Set Rows to repeat at top. * * @param array $rowsToRepeatAtTop Containing start column and end column, empty array if option unset * * @return $this */ public function setRowsToRepeatAtTop(array $rowsToRepeatAtTop) { $this->rowsToRepeatAtTop = $rowsToRepeatAtTop; return $this; } /** * Set Rows to repeat at top by start and end. * * @param int $start eg: 1 * @param int $end eg: 1 * * @return $this */ public function setRowsToRepeatAtTopByStartAndEnd($start, $end) { $this->rowsToRepeatAtTop = [$start, $end]; return $this; } /** * Get center page horizontally. * * @return bool */ public function getHorizontalCentered() { return $this->horizontalCentered; } /** * Set center page horizontally. * * @param bool $value * * @return $this */ public function setHorizontalCentered($value) { $this->horizontalCentered = $value; return $this; } /** * Get center page vertically. * * @return bool */ public function getVerticalCentered() { return $this->verticalCentered; } /** * Set center page vertically. * * @param bool $value * * @return $this */ public function setVerticalCentered($value) { $this->verticalCentered = $value; return $this; } /** * Get print area. * * @param int $index Identifier for a specific print area range if several ranges have been set * Default behaviour, or a index value of 0, will return all ranges as a comma-separated string * Otherwise, the specific range identified by the value of $index will be returned * Print areas are numbered from 1 * * @return string */ public function getPrintArea($index = 0) { if ($index == 0) { return (string) $this->printArea; } $printAreas = explode(',', (string) $this->printArea); if (isset($printAreas[$index - 1])) { return $printAreas[$index - 1]; } throw new PhpSpreadsheetException('Requested Print Area does not exist'); } /** * Is print area set? * * @param int $index Identifier for a specific print area range if several ranges have been set * Default behaviour, or an index value of 0, will identify whether any print range is set * Otherwise, existence of the range identified by the value of $index will be returned * Print areas are numbered from 1 * * @return bool */ public function isPrintAreaSet($index = 0) { if ($index == 0) { return $this->printArea !== null; } $printAreas = explode(',', (string) $this->printArea); return isset($printAreas[$index - 1]); } /** * Clear a print area. * * @param int $index Identifier for a specific print area range if several ranges have been set * Default behaviour, or an index value of 0, will clear all print ranges that are set * Otherwise, the range identified by the value of $index will be removed from the series * Print areas are numbered from 1 * * @return $this */ public function clearPrintArea($index = 0) { if ($index == 0) { $this->printArea = null; } else { $printAreas = explode(',', (string) $this->printArea); if (isset($printAreas[$index - 1])) { unset($printAreas[$index - 1]); $this->printArea = implode(',', $printAreas); } } return $this; } /** * Set print area. e.g. 'A1:D10' or 'A1:D10,G5:M20'. * * @param string $value * @param int $index Identifier for a specific print area range allowing several ranges to be set * When the method is "O"verwrite, then a positive integer index will overwrite that indexed * entry in the print areas list; a negative index value will identify which entry to * overwrite working bacward through the print area to the list, with the last entry as -1. * Specifying an index value of 0, will overwrite all existing print ranges. * When the method is "I"nsert, then a positive index will insert after that indexed entry in * the print areas list, while a negative index will insert before the indexed entry. * Specifying an index value of 0, will always append the new print range at the end of the * list. * Print areas are numbered from 1 * @param string $method Determines the method used when setting multiple print areas * Default behaviour, or the "O" method, overwrites existing print area * The "I" method, inserts the new print area before any specified index, or at the end of the list * * @return $this */ public function setPrintArea($value, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE) { if (strpos($value, '!') !== false) { throw new PhpSpreadsheetException('Cell coordinate must not specify a worksheet.'); } elseif (strpos($value, ':') === false) { throw new PhpSpreadsheetException('Cell coordinate must be a range of cells.'); } elseif (strpos($value, '$') !== false) { throw new PhpSpreadsheetException('Cell coordinate must not be absolute.'); } $value = strtoupper($value); if (!$this->printArea) { $index = 0; } if ($method == self::SETPRINTRANGE_OVERWRITE) { if ($index == 0) { $this->printArea = $value; } else { $printAreas = explode(',', (string) $this->printArea); if ($index < 0) { $index = count($printAreas) - abs($index) + 1; } if (($index <= 0) || ($index > count($printAreas))) { throw new PhpSpreadsheetException('Invalid index for setting print range.'); } $printAreas[$index - 1] = $value; $this->printArea = implode(',', $printAreas); } } elseif ($method == self::SETPRINTRANGE_INSERT) { if ($index == 0) { $this->printArea = $this->printArea ? ($this->printArea . ',' . $value) : $value; } else { $printAreas = explode(',', (string) $this->printArea); if ($index < 0) { $index = (int) abs($index) - 1; } if ($index > count($printAreas)) { throw new PhpSpreadsheetException('Invalid index for setting print range.'); } $printAreas = array_merge(array_slice($printAreas, 0, $index), [$value], array_slice($printAreas, $index)); $this->printArea = implode(',', $printAreas); } } else { throw new PhpSpreadsheetException('Invalid method for setting print range.'); } return $this; } /** * Add a new print area (e.g. 'A1:D10' or 'A1:D10,G5:M20') to the list of print areas. * * @param string $value * @param int $index Identifier for a specific print area range allowing several ranges to be set * A positive index will insert after that indexed entry in the print areas list, while a * negative index will insert before the indexed entry. * Specifying an index value of 0, will always append the new print range at the end of the * list. * Print areas are numbered from 1 * * @return $this */ public function addPrintArea($value, $index = -1) { return $this->setPrintArea($value, $index, self::SETPRINTRANGE_INSERT); } /** * Set print area. * * @param int $column1 Column 1 * @param int $row1 Row 1 * @param int $column2 Column 2 * @param int $row2 Row 2 * @param int $index Identifier for a specific print area range allowing several ranges to be set * When the method is "O"verwrite, then a positive integer index will overwrite that indexed * entry in the print areas list; a negative index value will identify which entry to * overwrite working backward through the print area to the list, with the last entry as -1. * Specifying an index value of 0, will overwrite all existing print ranges. * When the method is "I"nsert, then a positive index will insert after that indexed entry in * the print areas list, while a negative index will insert before the indexed entry. * Specifying an index value of 0, will always append the new print range at the end of the * list. * Print areas are numbered from 1 * @param string $method Determines the method used when setting multiple print areas * Default behaviour, or the "O" method, overwrites existing print area * The "I" method, inserts the new print area before any specified index, or at the end of the list * * @return $this */ public function setPrintAreaByColumnAndRow($column1, $row1, $column2, $row2, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE) { return $this->setPrintArea( Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2, $index, $method ); } /** * Add a new print area to the list of print areas. * * @param int $column1 Start Column for the print area * @param int $row1 Start Row for the print area * @param int $column2 End Column for the print area * @param int $row2 End Row for the print area * @param int $index Identifier for a specific print area range allowing several ranges to be set * A positive index will insert after that indexed entry in the print areas list, while a * negative index will insert before the indexed entry. * Specifying an index value of 0, will always append the new print range at the end of the * list. * Print areas are numbered from 1 * * @return $this */ public function addPrintAreaByColumnAndRow($column1, $row1, $column2, $row2, $index = -1) { return $this->setPrintArea( Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2, $index, self::SETPRINTRANGE_INSERT ); } /** * Get first page number. * * @return ?int */ public function getFirstPageNumber() { return $this->firstPageNumber; } /** * Set first page number. * * @param ?int $value * * @return $this */ public function setFirstPageNumber($value) { $this->firstPageNumber = $value; return $this; } /** * Reset first page number. * * @return $this */ public function resetFirstPageNumber() { return $this->setFirstPageNumber(null); } public function getPageOrder(): string { return $this->pageOrder; } public function setPageOrder(?string $pageOrder): self { if ($pageOrder === null || $pageOrder === self::PAGEORDER_DOWN_THEN_OVER || $pageOrder === self::PAGEORDER_OVER_THEN_DOWN) { $this->pageOrder = $pageOrder ?? self::PAGEORDER_DOWN_THEN_OVER; } return $this; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php000064400000007741152427537250017237 0ustar00worksheet = $worksheet; $this->columnIndex = $columnIndex; } /** * Destructor. */ public function __destruct() { // @phpstan-ignore-next-line $this->worksheet = null; } /** * Get column index as string eg: 'A'. */ public function getColumnIndex(): string { return $this->columnIndex; } /** * Get cell iterator. * * @param int $startRow The row number at which to start iterating * @param int $endRow Optionally, the row number at which to stop iterating */ public function getCellIterator($startRow = 1, $endRow = null): ColumnCellIterator { return new ColumnCellIterator($this->worksheet, $this->columnIndex, $startRow, $endRow); } /** * Get row iterator. Synonym for getCellIterator(). * * @param int $startRow The row number at which to start iterating * @param int $endRow Optionally, the row number at which to stop iterating */ public function getRowIterator($startRow = 1, $endRow = null): ColumnCellIterator { return $this->getCellIterator($startRow, $endRow); } /** * Returns a boolean true if the column contains no cells. By default, this means that no cell records exist in the * collection for this column. false will be returned otherwise. * This rule can be modified by passing a $definitionOfEmptyFlags value: * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value * cells, then the column will be considered empty. * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty * string value cells, then the column will be considered empty. * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * If the only cells in the collection are null value or empty string value cells, then the column * will be considered empty. * * @param int $definitionOfEmptyFlags * Possible Flag Values are: * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * @param int $startRow The row number at which to start checking if cells are empty * @param int $endRow Optionally, the row number at which to stop checking if cells are empty */ public function isEmpty(int $definitionOfEmptyFlags = 0, $startRow = 1, $endRow = null): bool { $nullValueCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL); $emptyStringCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL); $cellIterator = $this->getCellIterator($startRow, $endRow); $cellIterator->setIterateOnlyExistingCells(true); foreach ($cellIterator as $cell) { /** @scrutinizer ignore-call */ $value = $cell->getValue(); if ($value === null && $nullValueCellIsEmpty === true) { continue; } if ($value === '' && $emptyStringCellIsEmpty === true) { continue; } return false; } return true; } /** * Returns bound worksheet. */ public function getWorksheet(): Worksheet { return $this->worksheet; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php000064400000015251152427537250017370 0ustar00 IMAGETYPE_PNG, IMAGETYPE_JPEG => IMAGETYPE_JPEG, IMAGETYPE_PNG => IMAGETYPE_PNG, IMAGETYPE_BMP => IMAGETYPE_PNG, ]; /** * Path. * * @var string */ private $path; /** * Whether or not we are dealing with a URL. * * @var bool */ private $isUrl; /** * Create a new Drawing. */ public function __construct() { // Initialise values $this->path = ''; $this->isUrl = false; // Initialize parent parent::__construct(); } /** * Get Filename. * * @return string */ public function getFilename() { return basename($this->path); } /** * Get indexed filename (using image index). */ public function getIndexedFilename(): string { return md5($this->path) . '.' . $this->getExtension(); } /** * Get Extension. * * @return string */ public function getExtension() { $exploded = explode('.', basename($this->path)); return $exploded[count($exploded) - 1]; } /** * Get full filepath to store drawing in zip archive. * * @return string */ public function getMediaFilename() { if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); } return sprintf('image%d%s', $this->getImageIndex(), $this->getImageFileExtensionForSave()); } /** * Get Path. * * @return string */ public function getPath() { return $this->path; } /** * Set Path. * * @param string $path File path * @param bool $verifyFile Verify file * @param ZipArchive $zip Zip archive instance * * @return $this */ public function setPath($path, $verifyFile = true, $zip = null) { $this->isUrl = false; if (preg_match('~^data:image/[a-z]+;base64,~', $path) === 1) { $this->path = $path; return $this; } $this->path = ''; // Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979 if (filter_var($path, FILTER_VALIDATE_URL)) { if (!preg_match('/^(http|https|file|ftp|s3):/', $path)) { throw new PhpSpreadsheetException('Invalid protocol for linked drawing'); } // Implicit that it is a URL, rather store info than running check above on value in other places. $this->isUrl = true; $ctx = null; // https://github.com/php/php-src/issues/16023 if (substr($path, 0, 6) === 'https:') { $ctx = stream_context_create(['ssl' => ['crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT]]); } $imageContents = @file_get_contents($path, false, $ctx); if ($imageContents !== false) { $filePath = tempnam(sys_get_temp_dir(), 'Drawing'); if ($filePath) { $put = @file_put_contents($filePath, $imageContents); if ($put !== false) { if ($this->isImage($filePath)) { $this->path = $path; $this->setSizesAndType($filePath); } unlink($filePath); } } } } elseif ($zip instanceof ZipArchive) { $zipPath = explode('#', $path)[1]; $locate = @$zip->locateName($zipPath); if ($locate !== false) { if ($this->isImage($path)) { $this->path = $path; $this->setSizesAndType($path); } } } else { $exists = @file_exists($path); if ($exists !== false && $this->isImage($path)) { $this->path = $path; $this->setSizesAndType($path); } } if ($this->path === '' && $verifyFile) { throw new PhpSpreadsheetException("File $path not found!"); } return $this; } private function isImage(string $path): bool { $mime = (string) @mime_content_type($path); $retVal = false; if (strpos($mime, 'image/') === 0) { $retVal = true; } elseif ($mime === 'application/octet-stream') { $extension = pathinfo($path, PATHINFO_EXTENSION); $retVal = in_array($extension, ['bin', 'emf'], true); } return $retVal; } /** * Get isURL. */ public function getIsURL(): bool { return $this->isUrl; } /** * Set isURL. * * @return $this */ public function setIsURL(bool $isUrl): self { $this->isUrl = $isUrl; return $this; } /** * Get hash code. * * @return string Hash code */ public function getHashCode() { return md5( $this->path . parent::getHashCode() . __CLASS__ ); } /** * Get Image Type for Save. */ public function getImageTypeForSave(): int { if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); } return self::IMAGE_TYPES_CONVERTION_MAP[$this->type]; } /** * Get Image file extention for Save. */ public function getImageFileExtensionForSave(bool $includeDot = true): string { if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); } $result = image_type_to_extension(self::IMAGE_TYPES_CONVERTION_MAP[$this->type], $includeDot); return "$result"; } /** * Get Image mime type. */ public function getImageMimeType(): string { if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); } return image_type_to_mime_type(self::IMAGE_TYPES_CONVERTION_MAP[$this->type]); } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php000064400000007750152427537250016551 0ustar00worksheet = $worksheet; $this->rowIndex = $rowIndex; } /** * Destructor. */ public function __destruct() { $this->worksheet = null; // @phpstan-ignore-line } /** * Get row index. */ public function getRowIndex(): int { return $this->rowIndex; } /** * Get cell iterator. * * @param string $startColumn The column address at which to start iterating * @param string $endColumn Optionally, the column address at which to stop iterating */ public function getCellIterator($startColumn = 'A', $endColumn = null): RowCellIterator { return new RowCellIterator($this->worksheet, $this->rowIndex, $startColumn, $endColumn); } /** * Get column iterator. Synonym for getCellIterator(). * * @param string $startColumn The column address at which to start iterating * @param string $endColumn Optionally, the column address at which to stop iterating */ public function getColumnIterator($startColumn = 'A', $endColumn = null): RowCellIterator { return $this->getCellIterator($startColumn, $endColumn); } /** * Returns a boolean true if the row contains no cells. By default, this means that no cell records exist in the * collection for this row. false will be returned otherwise. * This rule can be modified by passing a $definitionOfEmptyFlags value: * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value * cells, then the row will be considered empty. * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty * string value cells, then the row will be considered empty. * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * If the only cells in the collection are null value or empty string value cells, then the row * will be considered empty. * * @param int $definitionOfEmptyFlags * Possible Flag Values are: * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * @param string $startColumn The column address at which to start checking if cells are empty * @param string $endColumn Optionally, the column address at which to stop checking if cells are empty */ public function isEmpty(int $definitionOfEmptyFlags = 0, $startColumn = 'A', $endColumn = null): bool { $nullValueCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL); $emptyStringCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL); $cellIterator = $this->getCellIterator($startColumn, $endColumn); $cellIterator->setIterateOnlyExistingCells(true); foreach ($cellIterator as $cell) { /** @scrutinizer ignore-call */ $value = $cell->getValue(); if ($value === null && $nullValueCellIsEmpty === true) { continue; } if ($value === '' && $emptyStringCellIsEmpty === true) { continue; } return false; } return true; } /** * Returns bound worksheet. */ public function getWorksheet(): Worksheet { return $this->worksheet; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php000064400000005252152427537250020412 0ustar00rowIndex = $index; // set dimension as unformatted by default parent::__construct(null); } /** * Get Row Index. */ public function getRowIndex(): ?int { return $this->rowIndex; } /** * Set Row Index. * * @return $this */ public function setRowIndex(int $index) { $this->rowIndex = $index; return $this; } /** * Get Row Height. * By default, this will be in points; but this method also accepts an optional unit of measure * argument, and will convert the value from points to the specified UoM. * A value of -1 tells Excel to display this column in its default height. * * @return float */ public function getRowHeight(?string $unitOfMeasure = null) { return ($unitOfMeasure === null || $this->height < 0) ? $this->height : (new CssDimension($this->height . CssDimension::UOM_POINTS))->toUnit($unitOfMeasure); } /** * Set Row Height. * * @param float $height in points. A value of -1 tells Excel to display this column in its default height. * By default, this will be the passed argument value; but this method also accepts an optional unit of measure * argument, and will convert the passed argument value to points from the specified UoM * * @return $this */ public function setRowHeight($height, ?string $unitOfMeasure = null) { $this->height = ($unitOfMeasure === null || $height < 0) ? $height : (new CssDimension("{$height}{$unitOfMeasure}"))->height(); return $this; } /** * Get ZeroHeight. */ public function getZeroHeight(): bool { return $this->zeroHeight; } /** * Set ZeroHeight. * * @return $this */ public function setZeroHeight(bool $zeroHeight) { $this->zeroHeight = $zeroHeight; return $this; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageBreak.php000064400000002535152427537250017617 0ustar00breakType = $breakType; $this->coordinate = $coordinate; $this->maxColOrRow = $maxColOrRow; } public function getBreakType(): int { return $this->breakType; } public function getCoordinate(): string { return $this->coordinate; } public function getMaxColOrRow(): int { return $this->maxColOrRow; } public function getColumnInt(): int { return Coordinate::indexesFromString($this->coordinate)[0]; } public function getRow(): int { return Coordinate::indexesFromString($this->coordinate)[1]; } public function getColumnString(): string { return Coordinate::indexesFromString($this->coordinate)[2]; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php000064400000026604152427537250020167 0ustar00setShadow(); // Set image index ++self::$imageCounter; $this->imageIndex = self::$imageCounter; } public function getImageIndex(): int { return $this->imageIndex; } public function getName(): string { return $this->name; } public function setName(string $name): self { $this->name = $name; return $this; } public function getDescription(): string { return $this->description; } public function setDescription(string $description): self { $this->description = $description; return $this; } public function getWorksheet(): ?Worksheet { return $this->worksheet; } /** * Set Worksheet. * * @param bool $overrideOld If a Worksheet has already been assigned, overwrite it and remove image from old Worksheet? */ public function setWorksheet(?Worksheet $worksheet = null, bool $overrideOld = false): self { if ($this->worksheet === null) { // Add drawing to \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet if ($worksheet !== null && !($this instanceof Drawing && $this->getPath() === '')) { $this->worksheet = $worksheet; $this->worksheet->getCell($this->coordinates); $this->worksheet->getDrawingCollection()->append($this); } } else { if ($overrideOld) { // Remove drawing from old \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $iterator = $this->worksheet->getDrawingCollection()->getIterator(); while ($iterator->valid()) { if ($iterator->current()->getHashCode() === $this->getHashCode()) { $this->worksheet->getDrawingCollection()->offsetUnset(/** @scrutinizer ignore-type */ $iterator->key()); $this->worksheet = null; break; } } // Set new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $this->setWorksheet($worksheet); } else { throw new PhpSpreadsheetException('A Worksheet has already been assigned. Drawings can only exist on one \\PhpOffice\\PhpSpreadsheet\\Worksheet.'); } } return $this; } public function getCoordinates(): string { return $this->coordinates; } public function setCoordinates(string $coordinates): self { $this->coordinates = $coordinates; return $this; } public function getOffsetX(): int { return $this->offsetX; } public function setOffsetX(int $offsetX): self { $this->offsetX = $offsetX; return $this; } public function getOffsetY(): int { return $this->offsetY; } public function setOffsetY(int $offsetY): self { $this->offsetY = $offsetY; return $this; } public function getCoordinates2(): string { return $this->coordinates2; } public function setCoordinates2(string $coordinates2): self { $this->coordinates2 = $coordinates2; return $this; } public function getOffsetX2(): int { return $this->offsetX2; } public function setOffsetX2(int $offsetX2): self { $this->offsetX2 = $offsetX2; return $this; } public function getOffsetY2(): int { return $this->offsetY2; } public function setOffsetY2(int $offsetY2): self { $this->offsetY2 = $offsetY2; return $this; } public function getWidth(): int { return $this->width; } public function setWidth(int $width): self { // Resize proportional? if ($this->resizeProportional && $width != 0) { $ratio = $this->height / ($this->width != 0 ? $this->width : 1); $this->height = (int) round($ratio * $width); } // Set width $this->width = $width; return $this; } public function getHeight(): int { return $this->height; } public function setHeight(int $height): self { // Resize proportional? if ($this->resizeProportional && $height != 0) { $ratio = $this->width / ($this->height != 0 ? $this->height : 1); $this->width = (int) round($ratio * $height); } // Set height $this->height = $height; return $this; } /** * Set width and height with proportional resize. * * Example: * * $objDrawing->setResizeProportional(true); * $objDrawing->setWidthAndHeight(160,120); * * * @author Vincent@luo MSN:kele_100@hotmail.com */ public function setWidthAndHeight(int $width, int $height): self { $xratio = $width / ($this->width != 0 ? $this->width : 1); $yratio = $height / ($this->height != 0 ? $this->height : 1); if ($this->resizeProportional && !($width == 0 || $height == 0)) { if (($xratio * $this->height) < $height) { $this->height = (int) ceil($xratio * $this->height); $this->width = $width; } else { $this->width = (int) ceil($yratio * $this->width); $this->height = $height; } } else { $this->width = $width; $this->height = $height; } return $this; } public function getResizeProportional(): bool { return $this->resizeProportional; } public function setResizeProportional(bool $resizeProportional): self { $this->resizeProportional = $resizeProportional; return $this; } public function getRotation(): int { return $this->rotation; } public function setRotation(int $rotation): self { $this->rotation = $rotation; return $this; } public function getShadow(): Drawing\Shadow { return $this->shadow; } public function setShadow(?Drawing\Shadow $shadow = null): self { $this->shadow = $shadow ?? new Drawing\Shadow(); return $this; } /** * Get hash code. * * @return string Hash code */ public function getHashCode() { return md5( $this->name . $this->description . (($this->worksheet === null) ? '' : $this->worksheet->getHashCode()) . $this->coordinates . $this->offsetX . $this->offsetY . $this->coordinates2 . $this->offsetX2 . $this->offsetY2 . $this->width . $this->height . $this->rotation . $this->shadow->getHashCode() . __CLASS__ ); } /** * 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) { if ($key == 'worksheet') { $this->worksheet = null; } elseif (is_object($value)) { $this->$key = clone $value; } else { $this->$key = $value; } } } public function setHyperlink(?Hyperlink $hyperlink = null): void { $this->hyperlink = $hyperlink; } public function getHyperlink(): ?Hyperlink { return $this->hyperlink; } /** * Set Fact Sizes and Type of Image. */ protected function setSizesAndType(string $path): void { if ($this->imageWidth === 0 && $this->imageHeight === 0 && $this->type === IMAGETYPE_UNKNOWN) { $imageData = getimagesize($path); if (!empty($imageData)) { $this->imageWidth = $imageData[0]; $this->imageHeight = $imageData[1]; $this->type = $imageData[2]; } } if ($this->width === 0 && $this->height === 0) { $this->width = $this->imageWidth; $this->height = $this->imageHeight; } } /** * Get Image Type. */ public function getType(): int { return $this->type; } public function getImageWidth(): int { return $this->imageWidth; } public function getImageHeight(): int { return $this->imageHeight; } public function getEditAs(): string { return $this->editAs; } public function setEditAs(string $editAs): self { $this->editAs = $editAs; return $this; } public function validEditAs(): bool { return in_array($this->editAs, self::VALID_EDIT_AS, true); } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php000064400000000676152427537250021665 0ustar00getPath() . $this->name . $this->offsetX . $this->offsetY . $this->width . $this->height . __CLASS__ ); } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php000064400000024647152427537250020134 0ustar00password !== '' || isset($this->sheet) || isset($this->objects) || isset($this->scenarios) || isset($this->formatCells) || isset($this->formatColumns) || isset($this->formatRows) || isset($this->insertColumns) || isset($this->insertRows) || isset($this->insertHyperlinks) || isset($this->deleteColumns) || isset($this->deleteRows) || isset($this->selectLockedCells) || isset($this->sort) || isset($this->autoFilter) || isset($this->pivotTables) || isset($this->selectUnlockedCells); } public function getSheet(): ?bool { return $this->sheet; } public function setSheet(?bool $sheet): self { $this->sheet = $sheet; return $this; } public function getObjects(): ?bool { return $this->objects; } public function setObjects(?bool $objects): self { $this->objects = $objects; return $this; } public function getScenarios(): ?bool { return $this->scenarios; } public function setScenarios(?bool $scenarios): self { $this->scenarios = $scenarios; return $this; } public function getFormatCells(): ?bool { return $this->formatCells; } public function setFormatCells(?bool $formatCells): self { $this->formatCells = $formatCells; return $this; } public function getFormatColumns(): ?bool { return $this->formatColumns; } public function setFormatColumns(?bool $formatColumns): self { $this->formatColumns = $formatColumns; return $this; } public function getFormatRows(): ?bool { return $this->formatRows; } public function setFormatRows(?bool $formatRows): self { $this->formatRows = $formatRows; return $this; } public function getInsertColumns(): ?bool { return $this->insertColumns; } public function setInsertColumns(?bool $insertColumns): self { $this->insertColumns = $insertColumns; return $this; } public function getInsertRows(): ?bool { return $this->insertRows; } public function setInsertRows(?bool $insertRows): self { $this->insertRows = $insertRows; return $this; } public function getInsertHyperlinks(): ?bool { return $this->insertHyperlinks; } public function setInsertHyperlinks(?bool $insertHyperLinks): self { $this->insertHyperlinks = $insertHyperLinks; return $this; } public function getDeleteColumns(): ?bool { return $this->deleteColumns; } public function setDeleteColumns(?bool $deleteColumns): self { $this->deleteColumns = $deleteColumns; return $this; } public function getDeleteRows(): ?bool { return $this->deleteRows; } public function setDeleteRows(?bool $deleteRows): self { $this->deleteRows = $deleteRows; return $this; } public function getSelectLockedCells(): ?bool { return $this->selectLockedCells; } public function setSelectLockedCells(?bool $selectLockedCells): self { $this->selectLockedCells = $selectLockedCells; return $this; } public function getSort(): ?bool { return $this->sort; } public function setSort(?bool $sort): self { $this->sort = $sort; return $this; } public function getAutoFilter(): ?bool { return $this->autoFilter; } public function setAutoFilter(?bool $autoFilter): self { $this->autoFilter = $autoFilter; return $this; } public function getPivotTables(): ?bool { return $this->pivotTables; } public function setPivotTables(?bool $pivotTables): self { $this->pivotTables = $pivotTables; return $this; } public function getSelectUnlockedCells(): ?bool { return $this->selectUnlockedCells; } public function setSelectUnlockedCells(?bool $selectUnlockedCells): self { $this->selectUnlockedCells = $selectUnlockedCells; return $this; } /** * Get hashed password. * * @return string */ public function getPassword() { return $this->password; } /** * Set Password. * * @param string $password * @param bool $alreadyHashed If the password has already been hashed, set this to true * * @return $this */ public function setPassword($password, $alreadyHashed = false) { if (!$alreadyHashed) { $salt = $this->generateSalt(); $this->setSalt($salt); $password = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount()); } $this->password = $password; return $this; } public function setHashValue(string $password): self { return $this->setPassword($password, true); } /** * Create a pseudorandom string. */ private function generateSalt(): string { return base64_encode(random_bytes(16)); } /** * Get algorithm name. */ public function getAlgorithm(): string { return $this->algorithm; } /** * Set algorithm name. */ public function setAlgorithm(string $algorithm): self { return $this->setAlgorithmName($algorithm); } /** * Set algorithm name. */ public function setAlgorithmName(string $algorithm): self { $this->algorithm = $algorithm; return $this; } public function getSalt(): string { return $this->salt; } public function setSalt(string $salt): self { return $this->setSaltValue($salt); } public function setSaltValue(string $salt): self { $this->salt = $salt; return $this; } /** * Get spin count. */ public function getSpinCount(): int { return $this->spinCount; } /** * Set spin count. */ public function setSpinCount(int $spinCount): self { $this->spinCount = $spinCount; return $this; } /** * Verify that the given non-hashed password can "unlock" the protection. */ public function verify(string $password): bool { if ($this->password === '') { return true; } $hash = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount()); return $this->getPassword() === $hash; } /** * 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) { if (is_object($value)) { $this->$key = clone $value; } else { $this->$key = $value; } } } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php000064400000011073152427537250020250 0ustar00|CellAddress|string $cellAddress Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. */ public static function validateCellAddress($cellAddress): string { if (is_string($cellAddress)) { [$worksheet, $address] = Worksheet::extractSheetTitle($cellAddress, true); // if (!empty($worksheet) && $worksheet !== $this->getTitle()) { // throw new Exception('Reference is not for this worksheet'); // } return empty($worksheet) ? strtoupper("$address") : $worksheet . '!' . strtoupper("$address"); } if (is_array($cellAddress)) { $cellAddress = CellAddress::fromColumnRowArray($cellAddress); } return (string) $cellAddress; } /** * Validate a cell address or cell range. * * @param AddressRange|array|CellAddress|int|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12'; * or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]), * or as a CellAddress or AddressRange object. */ public static function validateCellOrCellRange($cellRange): string { if (is_string($cellRange) || is_numeric($cellRange)) { // Convert a single column reference like 'A' to 'A:A', // a single row reference like '1' to '1:1' $cellRange = (string) preg_replace('/^([A-Z]+|\d+)$/', '${1}:${1}', (string) $cellRange); } elseif (is_object($cellRange) && $cellRange instanceof CellAddress) { $cellRange = new CellRange($cellRange, $cellRange); } return self::validateCellRange($cellRange); } private const SETMAXROW = '${1}1:${2}' . AddressRange::MAX_ROW; private const SETMAXCOL = 'A${1}:' . AddressRange::MAX_COLUMN . '${2}'; /** * Validate a cell range. * * @param AddressRange|array|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12'; * or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]), * or as an AddressRange object. */ public static function validateCellRange($cellRange): string { if (is_string($cellRange)) { [$worksheet, $addressRange] = Worksheet::extractSheetTitle($cellRange, true); // Convert Column ranges like 'A:C' to 'A1:C1048576' // or Row ranges like '1:3' to 'A1:XFD3' $addressRange = (string) preg_replace( ['/^([A-Z]+):([A-Z]+)$/i', '/^(\\d+):(\\d+)$/'], [self::SETMAXROW, self::SETMAXCOL], $addressRange ); return empty($worksheet) ? strtoupper($addressRange) : $worksheet . '!' . strtoupper($addressRange); } if (is_array($cellRange)) { switch (count($cellRange)) { case 2: $from = [$cellRange[0], $cellRange[1]]; $to = [$cellRange[0], $cellRange[1]]; break; case 4: $from = [$cellRange[0], $cellRange[1]]; $to = [$cellRange[2], $cellRange[3]]; break; default: throw new SpreadsheetException('CellRange array length must be 2 or 4'); } $cellRange = new CellRange(CellAddress::fromColumnRowArray($from), CellAddress::fromColumnRowArray($to)); } return (string) $cellRange; } public static function definedNameToCoordinate(string $coordinate, Worksheet $worksheet): string { // Uppercase coordinate $coordinate = strtoupper($coordinate); // Eliminate leading equal sign $testCoordinate = (string) preg_replace('/^=/', '', $coordinate); $defined = $worksheet->getParentOrThrow()->getDefinedName($testCoordinate, $worksheet); if ($defined !== null) { if ($defined->getWorksheet() === $worksheet && !$defined->isFormula()) { $coordinate = (string) preg_replace('/^=/', '', $defined->getValue()); } } return $coordinate; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php000064400000007700152427537250017700 0ustar00zoomScale; } /** * Set ZoomScale. * Valid values range from 10 to 400. * * @param ?int $zoomScale * * @return $this */ public function setZoomScale($zoomScale) { // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface, // but it is apparently still able to handle any scale >= 1 if ($zoomScale === null || $zoomScale >= 1) { $this->zoomScale = $zoomScale; } else { throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.'); } return $this; } /** * Get ZoomScaleNormal. * * @return ?int */ public function getZoomScaleNormal() { return $this->zoomScaleNormal; } /** * Set ZoomScale. * Valid values range from 10 to 400. * * @param ?int $zoomScaleNormal * * @return $this */ public function setZoomScaleNormal($zoomScaleNormal) { if ($zoomScaleNormal === null || $zoomScaleNormal >= 1) { $this->zoomScaleNormal = $zoomScaleNormal; } else { throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.'); } return $this; } /** * Set ShowZeroes setting. * * @param bool $showZeros */ public function setShowZeros($showZeros): void { $this->showZeros = $showZeros; } /** * @return bool */ public function getShowZeros() { return $this->showZeros; } /** * Get View. * * @return string */ public function getView() { return $this->sheetviewType; } /** * Set View. * * Valid values are * 'normal' self::SHEETVIEW_NORMAL * 'pageLayout' self::SHEETVIEW_PAGE_LAYOUT * 'pageBreakPreview' self::SHEETVIEW_PAGE_BREAK_PREVIEW * * @param ?string $sheetViewType * * @return $this */ public function setView($sheetViewType) { // MS Excel 2007 allows setting the view to 'normal', 'pageLayout' or 'pageBreakPreview' via the user interface if ($sheetViewType === null) { $sheetViewType = self::SHEETVIEW_NORMAL; } if (in_array($sheetViewType, self::SHEET_VIEW_TYPES)) { $this->sheetviewType = $sheetViewType; } else { throw new PhpSpreadsheetException('Invalid sheetview layout type.'); } return $this; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php000064400000004100152427537250020355 0ustar00 */ abstract class CellIterator implements NativeIterator { public const TREAT_NULL_VALUE_AS_EMPTY_CELL = 1; public const TREAT_EMPTY_STRING_AS_EMPTY_CELL = 2; public const IF_NOT_EXISTS_RETURN_NULL = false; public const IF_NOT_EXISTS_CREATE_NEW = true; /** * Worksheet to iterate. * * @var Worksheet */ protected $worksheet; /** * Cell Collection to iterate. * * @var Cells */ protected $cellCollection; /** * Iterate only existing cells. * * @var bool */ protected $onlyExistingCells = false; /** * If iterating all cells, and a cell doesn't exist, identifies whether a new cell should be created, * or if the iterator should return a null value. * * @var bool */ protected $ifNotExists = self::IF_NOT_EXISTS_CREATE_NEW; /** * Destructor. */ public function __destruct() { // @phpstan-ignore-next-line $this->worksheet = $this->cellCollection = null; } public function getIfNotExists(): bool { return $this->ifNotExists; } public function setIfNotExists(bool $ifNotExists = self::IF_NOT_EXISTS_CREATE_NEW): void { $this->ifNotExists = $ifNotExists; } /** * Get loop only existing cells. */ public function getIterateOnlyExistingCells(): bool { return $this->onlyExistingCells; } /** * Validate start/end values for 'IterateOnlyExistingCells' mode, and adjust if necessary. */ abstract protected function adjustForExistingOnlyRange(): void; /** * Set the iterator to loop only existing cells. */ public function setIterateOnlyExistingCells(bool $value): void { $this->onlyExistingCells = (bool) $value; $this->adjustForExistingOnlyRange(); } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php000064400000012650152427537250021544 0ustar00 */ class ColumnCellIterator extends CellIterator { /** * Current iterator position. * * @var int */ private $currentRow; /** * Column index. * * @var int */ private $columnIndex; /** * Start position. * * @var int */ private $startRow = 1; /** * End position. * * @var int */ private $endRow = 1; /** * Create a new row iterator. * * @param Worksheet $worksheet The worksheet to iterate over * @param string $columnIndex The column that we want to iterate * @param int $startRow The row number at which to start iterating * @param int $endRow Optionally, the row number at which to stop iterating */ public function __construct(Worksheet $worksheet, $columnIndex = 'A', $startRow = 1, $endRow = null) { // Set subject $this->worksheet = $worksheet; $this->cellCollection = $worksheet->getCellCollection(); $this->columnIndex = Coordinate::columnIndexFromString($columnIndex); $this->resetEnd($endRow); $this->resetStart($startRow); } /** * (Re)Set the start row and the current row pointer. * * @param int $startRow The row number at which to start iterating * * @return $this */ public function resetStart(int $startRow = 1) { $this->startRow = $startRow; $this->adjustForExistingOnlyRange(); $this->seek($startRow); return $this; } /** * (Re)Set the end row. * * @param int $endRow The row number at which to stop iterating * * @return $this */ public function resetEnd($endRow = null) { $this->endRow = $endRow ?: $this->worksheet->getHighestRow(); $this->adjustForExistingOnlyRange(); return $this; } /** * Set the row pointer to the selected row. * * @param int $row The row number to set the current pointer at * * @return $this */ public function seek(int $row = 1) { if ( $this->onlyExistingCells && (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->columnIndex) . $row)) ) { throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); } if (($row < $this->startRow) || ($row > $this->endRow)) { throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})"); } $this->currentRow = $row; return $this; } /** * Rewind the iterator to the starting row. */ public function rewind(): void { $this->currentRow = $this->startRow; } /** * Return the current cell in this worksheet column. */ public function current(): ?Cell { $cellAddress = Coordinate::stringFromColumnIndex($this->columnIndex) . $this->currentRow; return $this->cellCollection->has($cellAddress) ? $this->cellCollection->get($cellAddress) : ( $this->ifNotExists === self::IF_NOT_EXISTS_CREATE_NEW ? $this->worksheet->createNewCell($cellAddress) : null ); } /** * Return the current iterator key. */ public function key(): int { return $this->currentRow; } /** * Set the iterator to its next value. */ public function next(): void { $columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex); do { ++$this->currentRow; } while ( ($this->onlyExistingCells) && ($this->currentRow <= $this->endRow) && (!$this->cellCollection->has($columnAddress . $this->currentRow)) ); } /** * Set the iterator to its previous value. */ public function prev(): void { $columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex); do { --$this->currentRow; } while ( ($this->onlyExistingCells) && ($this->currentRow >= $this->startRow) && (!$this->cellCollection->has($columnAddress . $this->currentRow)) ); } /** * Indicate if more rows exist in the worksheet range of rows that we're iterating. */ public function valid(): bool { return $this->currentRow <= $this->endRow && $this->currentRow >= $this->startRow; } /** * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary. */ protected function adjustForExistingOnlyRange(): void { if ($this->onlyExistingCells) { $columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex); while ( (!$this->cellCollection->has($columnAddress . $this->startRow)) && ($this->startRow <= $this->endRow) ) { ++$this->startRow; } while ( (!$this->cellCollection->has($columnAddress . $this->endRow)) && ($this->endRow >= $this->startRow) ) { --$this->endRow; } } } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php000064400000026253152427537250020350 0ustar00 * Header/Footer Formatting Syntax taken from Office Open XML Part 4 - Markup Language Reference, page 1970:. * * There are a number of formatting codes that can be written inline with the actual header / footer text, which * affect the formatting in the header or footer. * * Example: This example shows the text "Center Bold Header" on the first line (center section), and the date on * the second line (center section). * &CCenter &"-,Bold"Bold&"-,Regular"Header_x000A_&D * * General Rules: * There is no required order in which these codes must appear. * * The first occurrence of the following codes turns the formatting ON, the second occurrence turns it OFF again: * - strikethrough * - superscript * - subscript * Superscript and subscript cannot both be ON at same time. Whichever comes first wins and the other is ignored, * while the first is ON. * &L - code for "left section" (there are three header / footer locations, "left", "center", and "right"). When * two or more occurrences of this section marker exist, the contents from all markers are concatenated, in the * order of appearance, and placed into the left section. * &P - code for "current page #" * &N - code for "total pages" * &font size - code for "text font size", where font size is a font size in points. * &K - code for "text font color" * RGB Color is specified as RRGGBB * Theme Color is specifed as TTSNN where TT is the theme color Id, S is either "+" or "-" of the tint/shade * value, NN is the tint/shade value. * &S - code for "text strikethrough" on / off * &X - code for "text super script" on / off * &Y - code for "text subscript" on / off * &C - code for "center section". When two or more occurrences of this section marker exist, the contents * from all markers are concatenated, in the order of appearance, and placed into the center section. * * &D - code for "date" * &T - code for "time" * &G - code for "picture as background" * &U - code for "text single underline" * &E - code for "double underline" * &R - code for "right section". When two or more occurrences of this section marker exist, the contents * from all markers are concatenated, in the order of appearance, and placed into the right section. * &Z - code for "this workbook's file path" * &F - code for "this workbook's file name" * &A - code for "sheet tab name" * &+ - code for add to page #. * &- - code for subtract from page #. * &"font name,font type" - code for "text font name" and "text font type", where font name and font type * are strings specifying the name and type of the font, separated by a comma. When a hyphen appears in font * name, it means "none specified". Both of font name and font type can be localized values. * &"-,Bold" - code for "bold font style" * &B - also means "bold font style". * &"-,Regular" - code for "regular font style" * &"-,Italic" - code for "italic font style" * &I - also means "italic font style" * &"-,Bold Italic" code for "bold italic font style" * &O - code for "outline style" * &H - code for "shadow style" * */ class HeaderFooter { // Header/footer image location const IMAGE_HEADER_LEFT = 'LH'; const IMAGE_HEADER_CENTER = 'CH'; const IMAGE_HEADER_RIGHT = 'RH'; const IMAGE_FOOTER_LEFT = 'LF'; const IMAGE_FOOTER_CENTER = 'CF'; const IMAGE_FOOTER_RIGHT = 'RF'; /** * OddHeader. * * @var string */ private $oddHeader = ''; /** * OddFooter. * * @var string */ private $oddFooter = ''; /** * EvenHeader. * * @var string */ private $evenHeader = ''; /** * EvenFooter. * * @var string */ private $evenFooter = ''; /** * FirstHeader. * * @var string */ private $firstHeader = ''; /** * FirstFooter. * * @var string */ private $firstFooter = ''; /** * Different header for Odd/Even, defaults to false. * * @var bool */ private $differentOddEven = false; /** * Different header for first page, defaults to false. * * @var bool */ private $differentFirst = false; /** * Scale with document, defaults to true. * * @var bool */ private $scaleWithDocument = true; /** * Align with margins, defaults to true. * * @var bool */ private $alignWithMargins = true; /** * Header/footer images. * * @var HeaderFooterDrawing[] */ private $headerFooterImages = []; /** * Create a new HeaderFooter. */ public function __construct() { } /** * Get OddHeader. * * @return string */ public function getOddHeader() { return $this->oddHeader; } /** * Set OddHeader. * * @param string $oddHeader * * @return $this */ public function setOddHeader($oddHeader) { $this->oddHeader = $oddHeader; return $this; } /** * Get OddFooter. * * @return string */ public function getOddFooter() { return $this->oddFooter; } /** * Set OddFooter. * * @param string $oddFooter * * @return $this */ public function setOddFooter($oddFooter) { $this->oddFooter = $oddFooter; return $this; } /** * Get EvenHeader. * * @return string */ public function getEvenHeader() { return $this->evenHeader; } /** * Set EvenHeader. * * @param string $eventHeader * * @return $this */ public function setEvenHeader($eventHeader) { $this->evenHeader = $eventHeader; return $this; } /** * Get EvenFooter. * * @return string */ public function getEvenFooter() { return $this->evenFooter; } /** * Set EvenFooter. * * @param string $evenFooter * * @return $this */ public function setEvenFooter($evenFooter) { $this->evenFooter = $evenFooter; return $this; } /** * Get FirstHeader. * * @return string */ public function getFirstHeader() { return $this->firstHeader; } /** * Set FirstHeader. * * @param string $firstHeader * * @return $this */ public function setFirstHeader($firstHeader) { $this->firstHeader = $firstHeader; return $this; } /** * Get FirstFooter. * * @return string */ public function getFirstFooter() { return $this->firstFooter; } /** * Set FirstFooter. * * @param string $firstFooter * * @return $this */ public function setFirstFooter($firstFooter) { $this->firstFooter = $firstFooter; return $this; } /** * Get DifferentOddEven. * * @return bool */ public function getDifferentOddEven() { return $this->differentOddEven; } /** * Set DifferentOddEven. * * @param bool $differentOddEvent * * @return $this */ public function setDifferentOddEven($differentOddEvent) { $this->differentOddEven = $differentOddEvent; return $this; } /** * Get DifferentFirst. * * @return bool */ public function getDifferentFirst() { return $this->differentFirst; } /** * Set DifferentFirst. * * @param bool $differentFirst * * @return $this */ public function setDifferentFirst($differentFirst) { $this->differentFirst = $differentFirst; return $this; } /** * Get ScaleWithDocument. * * @return bool */ public function getScaleWithDocument() { return $this->scaleWithDocument; } /** * Set ScaleWithDocument. * * @param bool $scaleWithDocument * * @return $this */ public function setScaleWithDocument($scaleWithDocument) { $this->scaleWithDocument = $scaleWithDocument; return $this; } /** * Get AlignWithMargins. * * @return bool */ public function getAlignWithMargins() { return $this->alignWithMargins; } /** * Set AlignWithMargins. * * @param bool $alignWithMargins * * @return $this */ public function setAlignWithMargins($alignWithMargins) { $this->alignWithMargins = $alignWithMargins; return $this; } /** * Add header/footer image. * * @param string $location * * @return $this */ public function addImage(HeaderFooterDrawing $image, $location = self::IMAGE_HEADER_LEFT) { $this->headerFooterImages[$location] = $image; return $this; } /** * Remove header/footer image. * * @param string $location * * @return $this */ public function removeImage($location = self::IMAGE_HEADER_LEFT) { if (isset($this->headerFooterImages[$location])) { unset($this->headerFooterImages[$location]); } return $this; } /** * Set header/footer images. * * @param HeaderFooterDrawing[] $images * * @return $this */ public function setImages(array $images) { $this->headerFooterImages = $images; return $this; } /** * Get header/footer images. * * @return HeaderFooterDrawing[] */ public function getImages() { // Sort array $images = []; if (isset($this->headerFooterImages[self::IMAGE_HEADER_LEFT])) { $images[self::IMAGE_HEADER_LEFT] = $this->headerFooterImages[self::IMAGE_HEADER_LEFT]; } if (isset($this->headerFooterImages[self::IMAGE_HEADER_CENTER])) { $images[self::IMAGE_HEADER_CENTER] = $this->headerFooterImages[self::IMAGE_HEADER_CENTER]; } if (isset($this->headerFooterImages[self::IMAGE_HEADER_RIGHT])) { $images[self::IMAGE_HEADER_RIGHT] = $this->headerFooterImages[self::IMAGE_HEADER_RIGHT]; } if (isset($this->headerFooterImages[self::IMAGE_FOOTER_LEFT])) { $images[self::IMAGE_FOOTER_LEFT] = $this->headerFooterImages[self::IMAGE_FOOTER_LEFT]; } if (isset($this->headerFooterImages[self::IMAGE_FOOTER_CENTER])) { $images[self::IMAGE_FOOTER_CENTER] = $this->headerFooterImages[self::IMAGE_FOOTER_CENTER]; } if (isset($this->headerFooterImages[self::IMAGE_FOOTER_RIGHT])) { $images[self::IMAGE_FOOTER_RIGHT] = $this->headerFooterImages[self::IMAGE_FOOTER_RIGHT]; } $this->headerFooterImages = $images; return $this->headerFooterImages; } /** * 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) { if (is_object($value)) { $this->$key = clone $value; } else { $this->$key = $value; } } } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php000064400000004361152427537250017722 0ustar00xfIndex = $initialValue; } /** * Get Visible. */ public function getVisible(): bool { return $this->visible; } /** * Set Visible. * * @return $this */ public function setVisible(bool $visible) { $this->visible = $visible; return $this; } /** * Get Outline Level. */ public function getOutlineLevel(): int { return $this->outlineLevel; } /** * Set Outline Level. * Value must be between 0 and 7. * * @return $this */ public function setOutlineLevel(int $level) { if ($level < 0 || $level > 7) { throw new PhpSpreadsheetException('Outline level must range between 0 and 7.'); } $this->outlineLevel = $level; return $this; } /** * Get Collapsed. */ public function getCollapsed(): bool { return $this->collapsed; } /** * Set Collapsed. * * @return $this */ public function setCollapsed(bool $collapsed) { $this->collapsed = $collapsed; return $this; } /** * Get index to cellXf. * * @return int */ public function getXfIndex(): ?int { return $this->xfIndex; } /** * Set index to cellXf. * * @return $this */ public function setXfIndex(int $XfIndex) { $this->xfIndex = $XfIndex; return $this; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php000064400000041616152427537250017030 0ustar00|string $range * A simple string containing a Cell range like 'A1:E10' is permitted * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. * @param string $name (e.g. Table1) */ public function __construct($range = '', string $name = '') { $this->style = new TableStyle(); $this->autoFilter = new AutoFilter($range); $this->setRange($range); $this->setName($name); } /** * Get Table name. */ public function getName(): string { return $this->name; } /** * Set Table name. * * @throws PhpSpreadsheetException */ public function setName(string $name): self { $name = trim($name); if (!empty($name)) { if (strlen($name) === 1 && in_array($name, ['C', 'c', 'R', 'r'])) { throw new PhpSpreadsheetException('The table name is invalid'); } if (StringHelper::countCharacters($name) > 255) { throw new PhpSpreadsheetException('The table name cannot be longer than 255 characters'); } // Check for A1 or R1C1 cell reference notation if ( preg_match(Coordinate::A1_COORDINATE_REGEX, $name) || preg_match('/^R\[?\-?[0-9]*\]?C\[?\-?[0-9]*\]?$/i', $name) ) { throw new PhpSpreadsheetException('The table name can\'t be the same as a cell reference'); } if (!preg_match('/^[\p{L}_\\\\]/iu', $name)) { throw new PhpSpreadsheetException('The table name must begin a name with a letter, an underscore character (_), or a backslash (\)'); } if (!preg_match('/^[\p{L}_\\\\][\p{L}\p{M}0-9\._]+$/iu', $name)) { throw new PhpSpreadsheetException('The table name contains invalid characters'); } $this->checkForDuplicateTableNames($name, $this->workSheet); $this->updateStructuredReferences($name); } $this->name = $name; return $this; } /** * @throws PhpSpreadsheetException */ private function checkForDuplicateTableNames(string $name, ?Worksheet $worksheet): void { // Remember that table names are case-insensitive $tableName = StringHelper::strToLower($name); if ($worksheet !== null && StringHelper::strToLower($this->name) !== $name) { $spreadsheet = $worksheet->getParentOrThrow(); foreach ($spreadsheet->getWorksheetIterator() as $sheet) { foreach ($sheet->getTableCollection() as $table) { if (StringHelper::strToLower($table->getName()) === $tableName && $table != $this) { throw new PhpSpreadsheetException("Spreadsheet already contains a table named '{$this->name}'"); } } } } } private function updateStructuredReferences(string $name): void { if ($this->workSheet === null || $this->name === null || $this->name === '') { return; } // Remember that table names are case-insensitive if (StringHelper::strToLower($this->name) !== StringHelper::strToLower($name)) { // We need to check all formula cells that might contain fully-qualified Structured References // that refer to this table, and update those formulae to reference the new table name $spreadsheet = $this->workSheet->getParentOrThrow(); foreach ($spreadsheet->getWorksheetIterator() as $sheet) { $this->updateStructuredReferencesInCells($sheet, $name); } $this->updateStructuredReferencesInNamedFormulae($spreadsheet, $name); } } private function updateStructuredReferencesInCells(Worksheet $worksheet, string $newName): void { $pattern = '/' . preg_quote($this->name, '/') . '\[/mui'; foreach ($worksheet->getCoordinates(false) as $coordinate) { $cell = $worksheet->getCell($coordinate); if ($cell->getDataType() === DataType::TYPE_FORMULA) { $formula = $cell->getValue(); if (preg_match($pattern, $formula) === 1) { $formula = preg_replace($pattern, "{$newName}[", $formula); $cell->setValueExplicit($formula, DataType::TYPE_FORMULA); } } } } private function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $newName): void { $pattern = '/' . preg_quote($this->name, '/') . '\[/mui'; foreach ($spreadsheet->getNamedFormulae() as $namedFormula) { $formula = $namedFormula->getValue(); if (preg_match($pattern, $formula) === 1) { $formula = preg_replace($pattern, "{$newName}[", $formula); $namedFormula->setValue($formula); // @phpstan-ignore-line } } } /** * Get show Header Row. */ public function getShowHeaderRow(): bool { return $this->showHeaderRow; } /** * Set show Header Row. */ public function setShowHeaderRow(bool $showHeaderRow): self { $this->showHeaderRow = $showHeaderRow; return $this; } /** * Get show Totals Row. */ public function getShowTotalsRow(): bool { return $this->showTotalsRow; } /** * Set show Totals Row. */ public function setShowTotalsRow(bool $showTotalsRow): self { $this->showTotalsRow = $showTotalsRow; return $this; } /** * Get allow filter. * If false, autofiltering is disabled for the table, if true it is enabled. */ public function getAllowFilter(): bool { return $this->allowFilter; } /** * Set show Autofiltering. * Disabling autofiltering has the same effect as hiding the filter button on all the columns in the table. */ public function setAllowFilter(bool $allowFilter): self { $this->allowFilter = $allowFilter; return $this; } /** * Get Table Range. */ public function getRange(): string { return $this->range; } /** * Set Table Cell Range. * * @param AddressRange|array|string $range * A simple string containing a Cell range like 'A1:E10' is permitted * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. */ public function setRange($range = ''): self { // extract coordinate if ($range !== '') { [, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true); } if (empty($range)) { // Discard all column rules $this->columns = []; $this->range = ''; return $this; } if (strpos($range, ':') === false) { throw new PhpSpreadsheetException('Table must be set on a range of cells.'); } [$width, $height] = Coordinate::rangeDimension($range); if ($width < 1 || $height < 1) { throw new PhpSpreadsheetException('The table range must be at least 1 column and row'); } $this->range = $range; $this->autoFilter->setRange($range); // Discard any column rules that are no longer valid within this range [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); foreach ($this->columns as $key => $value) { $colIndex = Coordinate::columnIndexFromString($key); if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) { unset($this->columns[$key]); } } return $this; } /** * Set Table Cell Range to max row. */ public function setRangeToMaxRow(): self { if ($this->workSheet !== null) { $thisrange = $this->range; $range = (string) preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange); if ($range !== $thisrange) { $this->setRange($range); } } return $this; } /** * Get Table's Worksheet. */ public function getWorksheet(): ?Worksheet { return $this->workSheet; } /** * Set Table's Worksheet. */ public function setWorksheet(?Worksheet $worksheet = null): self { if ($this->name !== '' && $worksheet !== null) { $spreadsheet = $worksheet->getParentOrThrow(); $tableName = StringHelper::strToUpper($this->name); foreach ($spreadsheet->getWorksheetIterator() as $sheet) { foreach ($sheet->getTableCollection() as $table) { if (StringHelper::strToUpper($table->getName()) === $tableName) { throw new PhpSpreadsheetException("Workbook already contains a table named '{$this->name}'"); } } } } $this->workSheet = $worksheet; $this->autoFilter->setParent($worksheet); return $this; } /** * Get all Table Columns. * * @return Table\Column[] */ public function getColumns(): array { return $this->columns; } /** * Validate that the specified column is in the Table range. * * @param string $column Column name (e.g. A) * * @return int The column offset within the table range */ public function isColumnInRange(string $column): int { if (empty($this->range)) { throw new PhpSpreadsheetException('No table range is defined.'); } $columnIndex = Coordinate::columnIndexFromString($column); [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) { throw new PhpSpreadsheetException('Column is outside of current table range.'); } return $columnIndex - $rangeStart[0]; } /** * Get a specified Table Column Offset within the defined Table range. * * @param string $column Column name (e.g. A) * * @return int The offset of the specified column within the table range */ public function getColumnOffset($column): int { return $this->isColumnInRange($column); } /** * Get a specified Table Column. * * @param string $column Column name (e.g. A) */ public function getColumn($column): Table\Column { $this->isColumnInRange($column); if (!isset($this->columns[$column])) { $this->columns[$column] = new Table\Column($column, $this); } return $this->columns[$column]; } /** * Get a specified Table Column by it's offset. * * @param int $columnOffset Column offset within range (starting from 0) */ public function getColumnByOffset($columnOffset): Table\Column { [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $columnOffset); return $this->getColumn($pColumn); } /** * Set Table. * * @param string|Table\Column $columnObjectOrString * A simple string containing a Column ID like 'A' is permitted */ public function setColumn($columnObjectOrString): self { if ((is_string($columnObjectOrString)) && (!empty($columnObjectOrString))) { $column = $columnObjectOrString; } elseif (is_object($columnObjectOrString) && ($columnObjectOrString instanceof Table\Column)) { $column = $columnObjectOrString->getColumnIndex(); } else { throw new PhpSpreadsheetException('Column is not within the table range.'); } $this->isColumnInRange($column); if (is_string($columnObjectOrString)) { $this->columns[$columnObjectOrString] = new Table\Column($columnObjectOrString, $this); } else { $columnObjectOrString->setTable($this); $this->columns[$column] = $columnObjectOrString; } ksort($this->columns); return $this; } /** * Clear a specified Table Column. * * @param string $column Column name (e.g. A) */ public function clearColumn($column): self { $this->isColumnInRange($column); if (isset($this->columns[$column])) { unset($this->columns[$column]); } return $this; } /** * Shift an Table Column Rule to a different column. * * Note: This method bypasses validation of the destination column to ensure it is within this Table range. * Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value. * Use with caution. * * @param string $fromColumn Column name (e.g. A) * @param string $toColumn Column name (e.g. B) */ public function shiftColumn($fromColumn, $toColumn): self { $fromColumn = strtoupper($fromColumn); $toColumn = strtoupper($toColumn); if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) { $this->columns[$fromColumn]->setTable(); $this->columns[$fromColumn]->setColumnIndex($toColumn); $this->columns[$toColumn] = $this->columns[$fromColumn]; $this->columns[$toColumn]->setTable($this); unset($this->columns[$fromColumn]); ksort($this->columns); } return $this; } /** * Get table Style. */ public function getStyle(): Table\TableStyle { return $this->style; } /** * Set table Style. */ public function setStyle(TableStyle $style): self { $this->style = $style; return $this; } /** * Get AutoFilter. */ public function getAutoFilter(): AutoFilter { return $this->autoFilter; } /** * Set AutoFilter. */ public function setAutoFilter(AutoFilter $autoFilter): self { $this->autoFilter = $autoFilter; return $this; } /** * 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) { if (is_object($value)) { if ($key === 'workSheet') { // Detach from worksheet $this->{$key} = null; } else { $this->{$key} = clone $value; } } elseif ((is_array($value)) && ($key === 'columns')) { // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table objects $this->{$key} = []; foreach ($value as $k => $v) { $this->{$key}[$k] = clone $v; // attach the new cloned Column to this new cloned Table object $this->{$key}[$k]->setTable($this); } } else { $this->{$key} = $value; } } } /** * toString method replicates previous behavior by returning the range if object is * referenced as a property of its worksheet. */ public function __toString() { return (string) $this->range; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php000064400000010612152427537250020740 0ustar00 */ class ColumnIterator implements NativeIterator { /** * Worksheet to iterate. * * @var Worksheet */ private $worksheet; /** * Current iterator position. * * @var int */ private $currentColumnIndex = 1; /** * Start position. * * @var int */ private $startColumnIndex = 1; /** * End position. * * @var int */ private $endColumnIndex = 1; /** * Create a new column iterator. * * @param Worksheet $worksheet The worksheet to iterate over * @param string $startColumn The column address at which to start iterating * @param string $endColumn Optionally, the column address at which to stop iterating */ public function __construct(Worksheet $worksheet, $startColumn = 'A', $endColumn = null) { // Set subject $this->worksheet = $worksheet; $this->resetEnd($endColumn); $this->resetStart($startColumn); } /** * Destructor. */ public function __destruct() { // @phpstan-ignore-next-line $this->worksheet = null; } /** * (Re)Set the start column and the current column pointer. * * @param string $startColumn The column address at which to start iterating * * @return $this */ public function resetStart(string $startColumn = 'A') { $startColumnIndex = Coordinate::columnIndexFromString($startColumn); if ($startColumnIndex > Coordinate::columnIndexFromString($this->worksheet->getHighestColumn())) { throw new Exception( "Start column ({$startColumn}) is beyond highest column ({$this->worksheet->getHighestColumn()})" ); } $this->startColumnIndex = $startColumnIndex; if ($this->endColumnIndex < $this->startColumnIndex) { $this->endColumnIndex = $this->startColumnIndex; } $this->seek($startColumn); return $this; } /** * (Re)Set the end column. * * @param string $endColumn The column address at which to stop iterating * * @return $this */ public function resetEnd($endColumn = null) { $endColumn = $endColumn ?: $this->worksheet->getHighestColumn(); $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn); return $this; } /** * Set the column pointer to the selected column. * * @param string $column The column address to set the current pointer at * * @return $this */ public function seek(string $column = 'A') { $column = Coordinate::columnIndexFromString($column); if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) { throw new PhpSpreadsheetException( "Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})" ); } $this->currentColumnIndex = $column; return $this; } /** * Rewind the iterator to the starting column. */ public function rewind(): void { $this->currentColumnIndex = $this->startColumnIndex; } /** * Return the current column in this worksheet. */ public function current(): Column { return new Column($this->worksheet, Coordinate::stringFromColumnIndex($this->currentColumnIndex)); } /** * Return the current iterator key. */ public function key(): string { return Coordinate::stringFromColumnIndex($this->currentColumnIndex); } /** * Set the iterator to its next value. */ public function next(): void { ++$this->currentColumnIndex; } /** * Set the iterator to its previous value. */ public function prev(): void { --$this->currentColumnIndex; } /** * Indicate if more columns exist in the worksheet range of columns that we're iterating. */ public function valid(): bool { return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php000064400000006455152427537250021106 0ustar00columnIndex = $index; // set dimension as unformatted by default parent::__construct(0); } /** * Get column index as string eg: 'A'. */ public function getColumnIndex(): ?string { return $this->columnIndex; } /** * Set column index as string eg: 'A'. */ public function setColumnIndex(string $index): self { $this->columnIndex = $index; return $this; } /** * Get column index as numeric. */ public function getColumnNumeric(): int { return Coordinate::columnIndexFromString($this->columnIndex ?? ''); } /** * Set column index as numeric. */ public function setColumnNumeric(int $index): self { $this->columnIndex = Coordinate::stringFromColumnIndex($index); return $this; } /** * Get Width. * * Each unit of column width is equal to the width of one character in the default font size. A value of -1 * tells Excel to display this column in its default width. * By default, this will be the return value; but this method also accepts an optional unit of measure argument * and will convert the returned value to the specified UoM.. */ public function getWidth(?string $unitOfMeasure = null): float { return ($unitOfMeasure === null || $this->width < 0) ? $this->width : (new CssDimension((string) $this->width))->toUnit($unitOfMeasure); } /** * Set Width. * * Each unit of column width is equal to the width of one character in the default font size. A value of -1 * tells Excel to display this column in its default width. * By default, this will be the unit of measure for the passed value; but this method also accepts an * optional unit of measure argument, and will convert the value from the specified UoM using an * approximation method. * * @return $this */ public function setWidth(float $width, ?string $unitOfMeasure = null) { $this->width = ($unitOfMeasure === null || $width < 0) ? $width : (new CssDimension("{$width}{$unitOfMeasure}"))->width(); return $this; } /** * Get Auto Size. */ public function getAutoSize(): bool { return $this->autoSize; } /** * Set Auto Size. * * @return $this */ public function setAutoSize(bool $autosizeEnabled) { $this->autoSize = $autosizeEnabled; return $this; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php000064400000021264152427537250020562 0ustar00renderingFunction = self::RENDERING_DEFAULT; $this->mimeType = self::MIMETYPE_DEFAULT; $this->uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999)); $this->alwaysNull = null; // Initialize parent parent::__construct(); } public function __destruct() { if ($this->imageResource) { $rslt = @imagedestroy($this->imageResource); // "Fix" for Scrutinizer $this->imageResource = $rslt ? null : $this->alwaysNull; } } public function __clone() { parent::__clone(); $this->cloneResource(); } private function cloneResource(): void { if (!$this->imageResource) { return; } $width = (int) imagesx($this->imageResource); $height = (int) imagesy($this->imageResource); if (imageistruecolor($this->imageResource)) { $clone = imagecreatetruecolor($width, $height); if (!$clone) { throw new Exception('Could not clone image resource'); } imagealphablending($clone, false); imagesavealpha($clone, true); } else { $clone = imagecreate($width, $height); if (!$clone) { throw new Exception('Could not clone image resource'); } // If the image has transparency... $transparent = imagecolortransparent($this->imageResource); if ($transparent >= 0) { $rgb = imagecolorsforindex($this->imageResource, $transparent); if (empty($rgb)) { throw new Exception('Could not get image colors'); } imagesavealpha($clone, true); $color = imagecolorallocatealpha($clone, $rgb['red'], $rgb['green'], $rgb['blue'], $rgb['alpha']); if ($color === false) { throw new Exception('Could not get image alpha color'); } imagefill($clone, 0, 0, $color); } } //Create the Clone!! imagecopy($clone, $this->imageResource, 0, 0, 0, 0, $width, $height); $this->imageResource = $clone; } /** * @param resource $imageStream Stream data to be converted to a Memory Drawing * * @throws Exception */ public static function fromStream($imageStream): self { $streamValue = stream_get_contents($imageStream); if ($streamValue === false) { throw new Exception('Unable to read data from stream'); } return self::fromString($streamValue); } /** * @param string $imageString String data to be converted to a Memory Drawing * * @throws Exception */ public static function fromString(string $imageString): self { $gdImage = @imagecreatefromstring($imageString); if ($gdImage === false) { throw new Exception('Value cannot be converted to an image'); } $mimeType = self::identifyMimeType($imageString); $renderingFunction = self::identifyRenderingFunction($mimeType); $drawing = new self(); $drawing->setImageResource($gdImage); $drawing->setRenderingFunction($renderingFunction); $drawing->setMimeType($mimeType); return $drawing; } private static function identifyRenderingFunction(string $mimeType): string { switch ($mimeType) { case self::MIMETYPE_PNG: return self::RENDERING_PNG; case self::MIMETYPE_JPEG: return self::RENDERING_JPEG; case self::MIMETYPE_GIF: return self::RENDERING_GIF; } return self::RENDERING_DEFAULT; } /** * @throws Exception */ private static function identifyMimeType(string $imageString): string { $temporaryFileName = File::temporaryFilename(); file_put_contents($temporaryFileName, $imageString); $mimeType = self::identifyMimeTypeUsingExif($temporaryFileName); if ($mimeType !== null) { unlink($temporaryFileName); return $mimeType; } $mimeType = self::identifyMimeTypeUsingGd($temporaryFileName); if ($mimeType !== null) { unlink($temporaryFileName); return $mimeType; } unlink($temporaryFileName); return self::MIMETYPE_DEFAULT; } private static function identifyMimeTypeUsingExif(string $temporaryFileName): ?string { if (function_exists('exif_imagetype')) { $imageType = @exif_imagetype($temporaryFileName); $mimeType = ($imageType) ? image_type_to_mime_type($imageType) : null; return self::supportedMimeTypes($mimeType); } return null; } private static function identifyMimeTypeUsingGd(string $temporaryFileName): ?string { if (function_exists('getimagesize')) { $imageSize = @getimagesize($temporaryFileName); if (is_array($imageSize)) { $mimeType = $imageSize['mime'] ?? null; return self::supportedMimeTypes($mimeType); } } return null; } private static function supportedMimeTypes(?string $mimeType = null): ?string { if (in_array($mimeType, self::SUPPORTED_MIME_TYPES, true)) { return $mimeType; } return null; } /** * Get image resource. * * @return null|GdImage|resource */ public function getImageResource() { return $this->imageResource; } /** * Set image resource. * * @param GdImage|resource $value * * @return $this */ public function setImageResource($value) { $this->imageResource = $value; if ($this->imageResource !== null) { // Get width/height $this->width = (int) imagesx($this->imageResource); $this->height = (int) imagesy($this->imageResource); } return $this; } /** * Get rendering function. * * @return string */ public function getRenderingFunction() { return $this->renderingFunction; } /** * Set rendering function. * * @param string $value see self::RENDERING_* * * @return $this */ public function setRenderingFunction($value) { $this->renderingFunction = $value; return $this; } /** * Get mime type. * * @return string */ public function getMimeType() { return $this->mimeType; } /** * Set mime type. * * @param string $value see self::MIMETYPE_* * * @return $this */ public function setMimeType($value) { $this->mimeType = $value; return $this; } /** * Get indexed filename (using image index). */ public function getIndexedFilename(): string { $extension = strtolower($this->getMimeType()); $extension = explode('/', $extension); $extension = $extension[1]; return $this->uniqueName . $this->getImageIndex() . '.' . $extension; } /** * Get hash code. * * @return string Hash code */ public function getHashCode() { return md5( $this->renderingFunction . $this->mimeType . $this->uniqueName . parent::getHashCode() . __CLASS__ ); } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php000064400000007034152427537250020256 0ustar00 */ class RowIterator implements NativeIterator { /** * Worksheet to iterate. * * @var Worksheet */ private $subject; /** * Current iterator position. * * @var int */ private $position = 1; /** * Start position. * * @var int */ private $startRow = 1; /** * End position. * * @var int */ private $endRow = 1; /** * Create a new row iterator. * * @param Worksheet $subject The worksheet to iterate over * @param int $startRow The row number at which to start iterating * @param int $endRow Optionally, the row number at which to stop iterating */ public function __construct(Worksheet $subject, $startRow = 1, $endRow = null) { // Set subject $this->subject = $subject; $this->resetEnd($endRow); $this->resetStart($startRow); } public function __destruct() { $this->subject = null; // @phpstan-ignore-line } /** * (Re)Set the start row and the current row pointer. * * @param int $startRow The row number at which to start iterating * * @return $this */ public function resetStart(int $startRow = 1) { if ($startRow > $this->subject->getHighestRow()) { throw new PhpSpreadsheetException( "Start row ({$startRow}) is beyond highest row ({$this->subject->getHighestRow()})" ); } $this->startRow = $startRow; if ($this->endRow < $this->startRow) { $this->endRow = $this->startRow; } $this->seek($startRow); return $this; } /** * (Re)Set the end row. * * @param int $endRow The row number at which to stop iterating * * @return $this */ public function resetEnd($endRow = null) { $this->endRow = $endRow ?: $this->subject->getHighestRow(); return $this; } /** * Set the row pointer to the selected row. * * @param int $row The row number to set the current pointer at * * @return $this */ public function seek(int $row = 1) { if (($row < $this->startRow) || ($row > $this->endRow)) { throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})"); } $this->position = $row; return $this; } /** * Rewind the iterator to the starting row. */ public function rewind(): void { $this->position = $this->startRow; } /** * Return the current row in this worksheet. */ public function current(): Row { return new Row($this->subject, $this->position); } /** * Return the current iterator key. */ public function key(): int { return $this->position; } /** * Set the iterator to its next value. */ public function next(): void { ++$this->position; } /** * Set the iterator to its previous value. */ public function prev(): void { --$this->position; } /** * Indicate if more rows exist in the worksheet range of rows that we're iterating. */ public function valid(): bool { return $this->position <= $this->endRow && $this->position >= $this->startRow; } } phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php000064400000121274152427537250020056 0ustar00evaluated; } public function setEvaluated(bool $value): void { $this->evaluated = $value; } /** * Create a new AutoFilter. * * @param AddressRange|array|string $range * A simple string containing a Cell range like 'A1:E10' is permitted * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. */ public function __construct($range = '', ?Worksheet $worksheet = null) { if ($range !== '') { [, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true); } $this->range = $range; $this->workSheet = $worksheet; } /** * Get AutoFilter Parent Worksheet. * * @return null|Worksheet */ public function getParent() { return $this->workSheet; } /** * Set AutoFilter Parent Worksheet. * * @return $this */ public function setParent(?Worksheet $worksheet = null) { $this->evaluated = false; $this->workSheet = $worksheet; return $this; } /** * Get AutoFilter Range. * * @return string */ public function getRange() { return $this->range; } /** * Set AutoFilter Cell Range. * * @param AddressRange|array|string $range * A simple string containing a Cell range like 'A1:E10' or a Cell address like 'A1' is permitted * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. */ public function setRange($range = ''): self { $this->evaluated = false; // extract coordinate if ($range !== '') { [, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true); } if (empty($range)) { // Discard all column rules $this->columns = []; $this->range = ''; return $this; } if (ctype_digit($range) || ctype_alpha($range)) { throw new Exception("{$range} is an invalid range for AutoFilter"); } $this->range = $range; // Discard any column rules that are no longer valid within this range [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); foreach ($this->columns as $key => $value) { $colIndex = Coordinate::columnIndexFromString($key); if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) { unset($this->columns[$key]); } } return $this; } public function setRangeToMaxRow(): self { $this->evaluated = false; if ($this->workSheet !== null) { $thisrange = $this->range; $range = (string) preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange); if ($range !== $thisrange) { $this->setRange($range); } } return $this; } /** * Get all AutoFilter Columns. * * @return AutoFilter\Column[] */ public function getColumns() { return $this->columns; } /** * Validate that the specified column is in the AutoFilter range. * * @param string $column Column name (e.g. A) * * @return int The column offset within the autofilter range */ public function testColumnInRange($column) { if (empty($this->range)) { throw new Exception('No autofilter range is defined.'); } $columnIndex = Coordinate::columnIndexFromString($column); [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) { throw new Exception('Column is outside of current autofilter range.'); } return $columnIndex - $rangeStart[0]; } /** * Get a specified AutoFilter Column Offset within the defined AutoFilter range. * * @param string $column Column name (e.g. A) * * @return int The offset of the specified column within the autofilter range */ public function getColumnOffset($column) { return $this->testColumnInRange($column); } /** * Get a specified AutoFilter Column. * * @param string $column Column name (e.g. A) * * @return AutoFilter\Column */ public function getColumn($column) { $this->testColumnInRange($column); if (!isset($this->columns[$column])) { $this->columns[$column] = new AutoFilter\Column($column, $this); } return $this->columns[$column]; } /** * Get a specified AutoFilter Column by it's offset. * * @param int $columnOffset Column offset within range (starting from 0) * * @return AutoFilter\Column */ public function getColumnByOffset($columnOffset) { [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $columnOffset); return $this->getColumn($pColumn); } /** * Set AutoFilter. * * @param AutoFilter\Column|string $columnObjectOrString * A simple string containing a Column ID like 'A' is permitted * * @return $this */ public function setColumn($columnObjectOrString) { $this->evaluated = false; if ((is_string($columnObjectOrString)) && (!empty($columnObjectOrString))) { $column = $columnObjectOrString; } elseif (is_object($columnObjectOrString) && ($columnObjectOrString instanceof AutoFilter\Column)) { $column = $columnObjectOrString->getColumnIndex(); } else { throw new Exception('Column is not within the autofilter range.'); } $this->testColumnInRange($column); if (is_string($columnObjectOrString)) { $this->columns[$columnObjectOrString] = new AutoFilter\Column($columnObjectOrString, $this); } else { $columnObjectOrString->setParent($this); $this->columns[$column] = $columnObjectOrString; } ksort($this->columns); return $this; } /** * Clear a specified AutoFilter Column. * * @param string $column Column name (e.g. A) * * @return $this */ public function clearColumn($column) { $this->evaluated = false; $this->testColumnInRange($column); if (isset($this->columns[$column])) { unset($this->columns[$column]); } return $this; } /** * Shift an AutoFilter Column Rule to a different column. * * Note: This method bypasses validation of the destination column to ensure it is within this AutoFilter range. * Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value. * Use with caution. * * @param string $fromColumn Column name (e.g. A) * @param string $toColumn Column name (e.g. B) * * @return $this */ public function shiftColumn($fromColumn, $toColumn) { $this->evaluated = false; $fromColumn = strtoupper($fromColumn); $toColumn = strtoupper($toColumn); if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) { $this->columns[$fromColumn]->setParent(); $this->columns[$fromColumn]->setColumnIndex($toColumn); $this->columns[$toColumn] = $this->columns[$fromColumn]; $this->columns[$toColumn]->setParent($this); unset($this->columns[$fromColumn]); ksort($this->columns); } return $this; } /** * Test if cell value is in the defined set of values. * * @param mixed $cellValue * @param mixed[] $dataSet * * @return bool */ protected static function filterTestInSimpleDataSet($cellValue, $dataSet) { $dataSetValues = $dataSet['filterValues']; $blanks = $dataSet['blanks']; if (($cellValue == '') || ($cellValue === null)) { return $blanks; } return in_array($cellValue, $dataSetValues); } /** * Test if cell value is in the defined set of Excel date values. * * @param mixed $cellValue * @param mixed[] $dataSet * * @return bool */ protected static function filterTestInDateGroupSet($cellValue, $dataSet) { $dateSet = $dataSet['filterValues']; $blanks = $dataSet['blanks']; if (($cellValue == '') || ($cellValue === null)) { return $blanks; } $timeZone = new DateTimeZone('UTC'); if (is_numeric($cellValue)) { $dateTime = Date::excelToDateTimeObject((float) $cellValue, $timeZone); $cellValue = (float) $cellValue; if ($cellValue < 1) { // Just the time part $dtVal = $dateTime->format('His'); $dateSet = $dateSet['time']; } elseif ($cellValue == floor($cellValue)) { // Just the date part $dtVal = $dateTime->format('Ymd'); $dateSet = $dateSet['date']; } else { // date and time parts $dtVal = $dateTime->format('YmdHis'); $dateSet = $dateSet['dateTime']; } foreach ($dateSet as $dateValue) { // Use of substr to extract value at the appropriate group level if (substr($dtVal, 0, strlen($dateValue)) == $dateValue) { return true; } } } return false; } /** * Test if cell value is within a set of values defined by a ruleset. * * @param mixed $cellValue * @param mixed[] $ruleSet * * @return bool */ protected static function filterTestInCustomDataSet($cellValue, $ruleSet) { /** @var array[] */ $dataSet = $ruleSet['filterRules']; $join = $ruleSet['join']; $customRuleForBlanks = $ruleSet['customRuleForBlanks'] ?? false; if (!$customRuleForBlanks) { // Blank cells are always ignored, so return a FALSE if (($cellValue == '') || ($cellValue === null)) { return false; } } $returnVal = ($join == AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND); foreach ($dataSet as $rule) { /** @var string */ $ruleValue = $rule['value']; /** @var string */ $ruleOperator = $rule['operator']; /** @var string */ $cellValueString = $cellValue ?? ''; $retVal = false; if (is_numeric($ruleValue)) { // Numeric values are tested using the appropriate operator $numericTest = is_numeric($cellValue); switch ($ruleOperator) { case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: $retVal = $numericTest && ($cellValue == $ruleValue); break; case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: $retVal = !$numericTest || ($cellValue != $ruleValue); break; case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN: $retVal = $numericTest && ($cellValue > $ruleValue); break; case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL: $retVal = $numericTest && ($cellValue >= $ruleValue); break; case Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN: $retVal = $numericTest && ($cellValue < $ruleValue); break; case Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL: $retVal = $numericTest && ($cellValue <= $ruleValue); break; } } elseif ($ruleValue == '') { switch ($ruleOperator) { case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: $retVal = (($cellValue == '') || ($cellValue === null)); break; case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: $retVal = (($cellValue != '') && ($cellValue !== null)); break; default: $retVal = true; break; } } else { // String values are always tested for equality, factoring in for wildcards (hence a regexp test) switch ($ruleOperator) { case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: $retVal = (bool) preg_match('/^' . $ruleValue . '$/i', $cellValueString); break; case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: $retVal = !((bool) preg_match('/^' . $ruleValue . '$/i', $cellValueString)); break; case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN: $retVal = strcasecmp($cellValueString, $ruleValue) > 0; break; case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL: $retVal = strcasecmp($cellValueString, $ruleValue) >= 0; break; case Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN: $retVal = strcasecmp($cellValueString, $ruleValue) < 0; break; case Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL: $retVal = strcasecmp($cellValueString, $ruleValue) <= 0; break; } } // If there are multiple conditions, then we need to test both using the appropriate join operator switch ($join) { case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR: $returnVal = $returnVal || $retVal; // Break as soon as we have a TRUE match for OR joins, // to avoid unnecessary additional code execution if ($returnVal) { return $returnVal; } break; case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND: $returnVal = $returnVal && $retVal; break; } } return $returnVal; } /** * Test if cell date value is matches a set of values defined by a set of months. * * @param mixed $cellValue * @param mixed[] $monthSet * * @return bool */ protected static function filterTestInPeriodDateSet($cellValue, $monthSet) { // Blank cells are always ignored, so return a FALSE if (($cellValue == '') || ($cellValue === null)) { return false; } if (is_numeric($cellValue)) { $dateObject = Date::excelToDateTimeObject((float) $cellValue, new DateTimeZone('UTC')); $dateValue = (int) $dateObject->format('m'); if (in_array($dateValue, $monthSet)) { return true; } } return false; } private static function makeDateObject(int $year, int $month, int $day, int $hour = 0, int $minute = 0, int $second = 0): DateTime { $baseDate = new DateTime(); $baseDate->setDate($year, $month, $day); $baseDate->setTime($hour, $minute, $second); return $baseDate; } private const DATE_FUNCTIONS = [ Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH => 'dynamicLastMonth', Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER => 'dynamicLastQuarter', Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK => 'dynamicLastWeek', Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR => 'dynamicLastYear', Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH => 'dynamicNextMonth', Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER => 'dynamicNextQuarter', Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK => 'dynamicNextWeek', Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR => 'dynamicNextYear', Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH => 'dynamicThisMonth', Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER => 'dynamicThisQuarter', Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK => 'dynamicThisWeek', Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR => 'dynamicThisYear', Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY => 'dynamicToday', Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW => 'dynamicTomorrow', Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE => 'dynamicYearToDate', Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY => 'dynamicYesterday', ]; private static function dynamicLastMonth(): array { $maxval = new DateTime(); $year = (int) $maxval->format('Y'); $month = (int) $maxval->format('m'); $maxval->setDate($year, $month, 1); $maxval->setTime(0, 0, 0); $val = clone $maxval; $val->modify('-1 month'); return [$val, $maxval]; } private static function firstDayOfQuarter(): DateTime { $val = new DateTime(); $year = (int) $val->format('Y'); $month = (int) $val->format('m'); $month = 3 * intdiv($month - 1, 3) + 1; $val->setDate($year, $month, 1); $val->setTime(0, 0, 0); return $val; } private static function dynamicLastQuarter(): array { $maxval = self::firstDayOfQuarter(); $val = clone $maxval; $val->modify('-3 months'); return [$val, $maxval]; } private static function dynamicLastWeek(): array { $val = new DateTime(); $val->setTime(0, 0, 0); $dayOfWeek = (int) $val->format('w'); // Sunday is 0 $subtract = $dayOfWeek + 7; // revert to prior Sunday $val->modify("-$subtract days"); $maxval = clone $val; $maxval->modify('+7 days'); return [$val, $maxval]; } private static function dynamicLastYear(): array { $val = new DateTime(); $year = (int) $val->format('Y'); $val = self::makeDateObject($year - 1, 1, 1); $maxval = self::makeDateObject($year, 1, 1); return [$val, $maxval]; } private static function dynamicNextMonth(): array { $val = new DateTime(); $year = (int) $val->format('Y'); $month = (int) $val->format('m'); $val->setDate($year, $month, 1); $val->setTime(0, 0, 0); $val->modify('+1 month'); $maxval = clone $val; $maxval->modify('+1 month'); return [$val, $maxval]; } private static function dynamicNextQuarter(): array { $val = self::firstDayOfQuarter(); $val->modify('+3 months'); $maxval = clone $val; $maxval->modify('+3 months'); return [$val, $maxval]; } private static function dynamicNextWeek(): array { $val = new DateTime(); $val->setTime(0, 0, 0); $dayOfWeek = (int) $val->format('w'); // Sunday is 0 $add = 7 - $dayOfWeek; // move to next Sunday $val->modify("+$add days"); $maxval = clone $val; $maxval->modify('+7 days'); return [$val, $maxval]; } private static function dynamicNextYear(): array { $val = new DateTime(); $year = (int) $val->format('Y'); $val = self::makeDateObject($year + 1, 1, 1); $maxval = self::makeDateObject($year + 2, 1, 1); return [$val, $maxval]; } private static function dynamicThisMonth(): array { $baseDate = new DateTime(); $baseDate->setTime(0, 0, 0); $year = (int) $baseDate->format('Y'); $month = (int) $baseDate->format('m'); $val = self::makeDateObject($year, $month, 1); $maxval = clone $val; $maxval->modify('+1 month'); return [$val, $maxval]; } private static function dynamicThisQuarter(): array { $val = self::firstDayOfQuarter(); $maxval = clone $val; $maxval->modify('+3 months'); return [$val, $maxval]; } private static function dynamicThisWeek(): array { $val = new DateTime(); $val->setTime(0, 0, 0); $dayOfWeek = (int) $val->format('w'); // Sunday is 0 $subtract = $dayOfWeek; // revert to Sunday $val->modify("-$subtract days"); $maxval = clone $val; $maxval->modify('+7 days'); return [$val, $maxval]; } private static function dynamicThisYear(): array { $val = new DateTime(); $year = (int) $val->format('Y'); $val = self::makeDateObject($year, 1, 1); $maxval = self::makeDateObject($year + 1, 1, 1); return [$val, $maxval]; } private static function dynamicToday(): array { $val = new DateTime(); $val->setTime(0, 0, 0); $maxval = clone $val; $maxval->modify('+1 day'); return [$val, $maxval]; } private static function dynamicTomorrow(): array { $val = new DateTime(); $val->setTime(0, 0, 0); $val->modify('+1 day'); $maxval = clone $val; $maxval->modify('+1 day'); return [$val, $maxval]; } private static function dynamicYearToDate(): array { $maxval = new DateTime(); $maxval->setTime(0, 0, 0); $val = self::makeDateObject((int) $maxval->format('Y'), 1, 1); $maxval->modify('+1 day'); return [$val, $maxval]; } private static function dynamicYesterday(): array { $maxval = new DateTime(); $maxval->setTime(0, 0, 0); $val = clone $maxval; $val->modify('-1 day'); return [$val, $maxval]; } /** * Convert a dynamic rule daterange to a custom filter range expression for ease of calculation. * * @param string $dynamicRuleType * * @return mixed[] */ private function dynamicFilterDateRange($dynamicRuleType, AutoFilter\Column &$filterColumn) { $ruleValues = []; $callBack = [__CLASS__, self::DATE_FUNCTIONS[$dynamicRuleType]]; // What if not found? // Calculate start/end dates for the required date range based on current date // Val is lowest permitted value. // Maxval is greater than highest permitted value $val = $maxval = 0; if (is_callable($callBack)) { [$val, $maxval] = $callBack(); } $val = Date::dateTimeToExcel($val); $maxval = Date::dateTimeToExcel($maxval); // Set the filter column rule attributes ready for writing $filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxval]); // Set the rules for identifying rows for hide/show $ruleValues[] = ['operator' => Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val]; $ruleValues[] = ['operator' => Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxval]; return ['method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND]]; } /** * Apply the AutoFilter rules to the AutoFilter Range. * * @param string $columnID * @param int $startRow * @param int $endRow * @param ?string $ruleType * @param mixed $ruleValue * * @return mixed */ private function calculateTopTenValue($columnID, $startRow, $endRow, $ruleType, $ruleValue) { $range = $columnID . $startRow . ':' . $columnID . $endRow; $retVal = null; if ($this->workSheet !== null) { $dataValues = Functions::flattenArray($this->workSheet->rangeToArray($range, null, true, false)); $dataValues = array_filter($dataValues); if ($ruleType == Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) { rsort($dataValues); } else { sort($dataValues); } $slice = array_slice($dataValues, 0, $ruleValue); $retVal = array_pop($slice); } return $retVal; } /** * Apply the AutoFilter rules to the AutoFilter Range. * * @return $this */ public function showHideRows() { if ($this->workSheet === null) { return $this; } [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); // The heading row should always be visible $this->workSheet->getRowDimension($rangeStart[1])->setVisible(true); $columnFilterTests = []; foreach ($this->columns as $columnID => $filterColumn) { $rules = $filterColumn->getRules(); switch ($filterColumn->getFilterType()) { case AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER: $ruleType = null; $ruleValues = []; // Build a list of the filter value selections foreach ($rules as $rule) { $ruleType = $rule->getRuleType(); $ruleValues[] = $rule->getValue(); } // Test if we want to include blanks in our filter criteria $blanks = false; $ruleDataSet = array_filter($ruleValues); if (count($ruleValues) != count($ruleDataSet)) { $blanks = true; } if ($ruleType == Rule::AUTOFILTER_RULETYPE_FILTER) { // Filter on absolute values $columnFilterTests[$columnID] = [ 'method' => 'filterTestInSimpleDataSet', 'arguments' => ['filterValues' => $ruleDataSet, 'blanks' => $blanks], ]; } else { // Filter on date group values $arguments = [ 'date' => [], 'time' => [], 'dateTime' => [], ]; foreach ($ruleDataSet as $ruleValue) { if (!is_array($ruleValue)) { continue; } $date = $time = ''; if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '') ) { $date .= sprintf('%04d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]); } if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '') ) { $date .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]); } if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '') ) { $date .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]); } if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '') ) { $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]); } if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '') ) { $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]); } if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '') ) { $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]); } $dateTime = $date . $time; $arguments['date'][] = $date; $arguments['time'][] = $time; $arguments['dateTime'][] = $dateTime; } // Remove empty elements $arguments['date'] = array_filter($arguments['date']); $arguments['time'] = array_filter($arguments['time']); $arguments['dateTime'] = array_filter($arguments['dateTime']); $columnFilterTests[$columnID] = [ 'method' => 'filterTestInDateGroupSet', 'arguments' => ['filterValues' => $arguments, 'blanks' => $blanks], ]; } break; case AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER: $customRuleForBlanks = true; $ruleValues = []; // Build a list of the filter value selections foreach ($rules as $rule) { $ruleValue = $rule->getValue(); if (!is_array($ruleValue) && !is_numeric($ruleValue)) { // Convert to a regexp allowing for regexp reserved characters, wildcards and escaped wildcards $ruleValue = WildcardMatch::wildcard($ruleValue); if (trim($ruleValue) == '') { $customRuleForBlanks = true; $ruleValue = trim($ruleValue); } } $ruleValues[] = ['operator' => $rule->getOperator(), 'value' => $ruleValue]; } $join = $filterColumn->getJoin(); $columnFilterTests[$columnID] = [ 'method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => $join, 'customRuleForBlanks' => $customRuleForBlanks], ]; break; case AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER: $ruleValues = []; foreach ($rules as $rule) { // We should only ever have one Dynamic Filter Rule anyway $dynamicRuleType = $rule->getGrouping(); if ( ($dynamicRuleType == Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) || ($dynamicRuleType == Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE) ) { // Number (Average) based // Calculate the average $averageFormula = '=AVERAGE(' . $columnID . ($rangeStart[1] + 1) . ':' . $columnID . $rangeEnd[1] . ')'; $spreadsheet = ($this->workSheet === null) ? null : $this->workSheet->getParent(); $average = Calculation::getInstance($spreadsheet)->calculateFormula($averageFormula, null, $this->workSheet->getCell('A1')); while (is_array($average)) { $average = array_pop($average); } // Set above/below rule based on greaterThan or LessTan $operator = ($dynamicRuleType === Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) ? Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN : Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN; $ruleValues[] = [ 'operator' => $operator, 'value' => $average, ]; $columnFilterTests[$columnID] = [ 'method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR], ]; } else { // Date based if ($dynamicRuleType[0] == 'M' || $dynamicRuleType[0] == 'Q') { $periodType = ''; $period = 0; // Month or Quarter sscanf($dynamicRuleType, '%[A-Z]%d', $periodType, $period); if ($periodType == 'M') { $ruleValues = [$period]; } else { --$period; $periodEnd = (1 + $period) * 3; $periodStart = 1 + $period * 3; $ruleValues = range($periodStart, $periodEnd); } $columnFilterTests[$columnID] = [ 'method' => 'filterTestInPeriodDateSet', 'arguments' => $ruleValues, ]; $filterColumn->setAttributes([]); } else { // Date Range $columnFilterTests[$columnID] = $this->dynamicFilterDateRange($dynamicRuleType, $filterColumn); break; } } } break; case AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER: $ruleValues = []; $dataRowCount = $rangeEnd[1] - $rangeStart[1]; $toptenRuleType = null; $ruleValue = 0; $ruleOperator = null; foreach ($rules as $rule) { // We should only ever have one Dynamic Filter Rule anyway $toptenRuleType = $rule->getGrouping(); $ruleValue = $rule->getValue(); $ruleOperator = $rule->getOperator(); } if (is_numeric($ruleValue) && $ruleOperator === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) { $ruleValue = floor((float) $ruleValue * ($dataRowCount / 100)); } if (!is_array($ruleValue) && $ruleValue < 1) { $ruleValue = 1; } if (!is_array($ruleValue) && $ruleValue > 500) { $ruleValue = 500; } $maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, (int) $rangeEnd[1], $toptenRuleType, $ruleValue); $operator = ($toptenRuleType == Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) ? Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL : Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL; $ruleValues[] = ['operator' => $operator, 'value' => $maxVal]; $columnFilterTests[$columnID] = [ 'method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR], ]; $filterColumn->setAttributes(['maxVal' => $maxVal]); break; } } $rangeEnd[1] = $this->autoExtendRange($rangeStart[1], $rangeEnd[1]); // Execute the column tests for each row in the autoFilter range to determine show/hide, for ($row = $rangeStart[1] + 1; $row <= $rangeEnd[1]; ++$row) { $result = true; foreach ($columnFilterTests as $columnID => $columnFilterTest) { $cellValue = $this->workSheet->getCell($columnID . $row)->getCalculatedValue(); // Execute the filter test $result = // $result && // phpstan says $result is always true here // @phpstan-ignore-next-line call_user_func_array([self::class, $columnFilterTest['method']], [$cellValue, $columnFilterTest['arguments']]); // If filter test has resulted in FALSE, exit the loop straightaway rather than running any more tests if (!$result) { break; } } // Set show/hide for the row based on the result of the autoFilter result $this->workSheet->getRowDimension((int) $row)->setVisible($result); } $this->evaluated = true; return $this; } /** * Magic Range Auto-sizing. * For a single row rangeSet, we follow MS Excel rules, and search for the first empty row to determine our range. */ public function autoExtendRange(int $startRow, int $endRow): int { if ($startRow === $endRow && $this->workSheet !== null) { try { $rowIterator = $this->workSheet->getRowIterator($startRow + 1); } catch (Exception $e) { // If there are no rows below $startRow return $startRow; } foreach ($rowIterator as $row) { if ($row->isEmpty(CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL) === true) { return $row->getRowIndex() - 1; } } } return $endRow; } /** * 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) { if (is_object($value)) { if ($key === 'workSheet') { // Detach from worksheet $this->{$key} = null; } else { $this->{$key} = clone $value; } } elseif ((is_array($value)) && ($key == 'columns')) { // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects $this->{$key} = []; foreach ($value as $k => $v) { $this->{$key}[$k] = clone $v; // attach the new cloned Column to this new cloned Autofilter object $this->{$key}[$k]->setParent($this); } } else { $this->{$key} = $value; } } } /** * toString method replicates previous behavior by returning the range if object is * referenced as a property of its parent. */ public function __toString() { return (string) $this->range; } } phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt000064400000001076152427537250022725 0ustar00ChartDirector 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/IRenderer.php000064400000000662152427537250020510 0ustar00graph = 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, ]; } /** * 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($groupID, $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($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($dataValues, $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($type = 'textlin'): void { $this->graph = new Graph(self::$width, self::$height); $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(self::$width, self::$height); $this->renderTitle(); } private function renderRadarPlotArea(): void { $this->graph = new RadarGraph(self::$width, self::$height); $this->graph->SetScale('lin'); $this->renderTitle(); } private function renderPlotLine($groupID, $filled = false, $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($groupID, $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($groupID, $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; } } //var_dump($dataValuesY, $dataValuesX, $bubbleSize); $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) * self::$width / 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($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($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($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, $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, $dimensions = '2d', $doughnut = false, $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) { $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($outputDestination) { 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
'; 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
'; return false; } $this->renderLegend(); $this->graph->Stroke($outputDestination); return true; } } phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php000064400000075237152427537250017231 0ustar00 null, ]; /** @var array */ protected $shadowProperties = self::PRESETS_OPTIONS[0]; /** @var ChartColor */ protected $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. * * @return bool */ public function getObjectState() { 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; } /** * @param null|float|int|string $alpha */ protected function setColorProperties(?string $color, $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. * * @param mixed $properties * @param mixed $elements * * @return mixed */ protected function getArrayElementsValue($properties, $elements) { $reference = &$properties; if (!is_array($elements)) { return $reference[$elements]; } foreach ($elements as $keys) { $reference = &$reference[$keys]; } return $reference; } /** * Set Glow Properties. * * @param float $size * @param ?string $colorValue * @param ?int $colorAlpha * @param ?string $colorType */ public function setGlowProperties($size, $colorValue = null, $colorAlpha = null, $colorType = null): void { $this ->activateObject() ->setGlowSize($size); $this->glowColor->setColorPropertiesArray( [ 'value' => $colorValue, 'type' => $colorType, 'alpha' => $colorAlpha, ] ); } /** * Get Glow Property. * * @param array|string $property * * @return null|array|float|int|string */ public function getGlowProperty($property) { $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. * * @param string $propertyName * * @return null|int|string */ public function getGlowColor($propertyName) { return $this->glowColor->getColorProperty($propertyName); } public function getGlowColorObject(): ChartColor { return $this->glowColor; } /** * Get Glow Size. * * @return ?float */ public function getGlowSize() { return $this->glowSize; } /** * Set Glow Size. * * @param ?float $size * * @return $this */ protected function setGlowSize($size) { $this->glowSize = $size; return $this; } /** * Set Soft Edges Size. * * @param ?float $size */ public function setSoftEdges($size): void { if ($size !== null) { $this->activateObject(); $this->softEdges['size'] = $size; } } /** * Get Soft Edges Size. * * @return string */ public function getSoftEdgesSize() { return $this->softEdges['size']; } /** * @param mixed $value */ public function setShadowProperty(string $propertyName, $value): self { $this->activateObject(); if ($propertyName === 'color' && is_array($value)) { $this->shadowColor->setColorPropertiesArray($value); } else { $this->shadowProperties[$propertyName] = $value; } return $this; } /** * Set Shadow Properties. * * @param int $presets * @param string $colorValue * @param string $colorType * @param null|float|int|string $colorAlpha * @param null|float $blur * @param null|int $angle * @param null|float $distance */ public function setShadowProperties($presets, $colorValue = null, $colorType = null, $colorAlpha = null, $blur = null, $angle = null, $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. * * @param int $presets * * @return $this */ protected function setShadowPresetsProperties($presets) { $this->shadowProperties['presets'] = $presets; $this->setShadowPropertiesMapValues($this->getShadowPresetsMap($presets)); return $this; } protected const SHADOW_ARRAY_KEYS = ['size', 'color']; /** * Set Shadow Properties Values. * * @param mixed $reference * * @return $this */ protected function setShadowPropertiesMapValues(array $propertiesMap, &$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. * * @param ?float $blur * * @return $this */ protected function setShadowBlur($blur) { if ($blur !== null) { $this->shadowProperties['blur'] = $blur; } return $this; } /** * Set Shadow Angle. * * @param null|float|int|string $angle * * @return $this */ protected function setShadowAngle($angle) { if (is_numeric($angle)) { $this->shadowProperties['direction'] = $angle; } return $this; } /** * Set Shadow Distance. * * @param ?float $distance * * @return $this */ protected function setShadowDistance($distance) { if ($distance !== null) { $this->shadowProperties['distance'] = $distance; } return $this; } public function getShadowColorObject(): ChartColor { return $this->shadowColor; } /** * Get Shadow Property. * * @param string|string[] $elements * * @return array|string */ public function getShadowProperty($elements) { 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; } /** @var ChartColor */ protected $lineColor; /** @var array */ protected $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. * * @param string $value * @param ?int $alpha * @param ?string $colorType */ public function setLineColorProperties($value, $alpha = null, $colorType = null): void { $this->activateObject(); $this->lineColor->setColorPropertiesArray( $this->setColorProperties( $value, $alpha, $colorType ) ); } /** * Get Line Color Property. * * @param string $propertyName * * @return null|int|string */ public function getLineColorProperty($propertyName) { return $this->lineColor->getColorProperty($propertyName); } /** * Set Line Style Properties. * * @param null|float|int|string $lineWidth * @param string $compoundType * @param string $dashType * @param string $capType * @param string $joinType * @param string $headArrowType * @param string $headArrowSize * @param string $endArrowType * @param string $endArrowSize * @param string $headArrowWidth * @param string $headArrowLength * @param string $endArrowWidth * @param string $endArrowLength */ public function setLineStyleProperties($lineWidth = null, $compoundType = '', $dashType = '', $capType = '', $joinType = '', $headArrowType = '', $headArrowSize = '', $endArrowType = '', $endArrowSize = '', $headArrowWidth = '', $headArrowLength = '', $endArrowWidth = '', $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 (array_key_exists($headArrowSize, self::ARROW_SIZES)) { $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 (array_key_exists($endArrowSize, self::ARROW_SIZES)) { $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; } /** * @param mixed $value */ public function setLineStyleProperty(string $propertyName, $value): self { $this->activateObject(); $this->lineStyleProperties[$propertyName] = $value; return $this; } /** * Get Line Style Property. * * @param array|string $elements * * @return string */ public function getLineStyleProperty($elements) { 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. * * @param int $arraySelector * @param string $arrayKaySelector * * @return string */ protected function getLineStyleArrowSize($arraySelector, $arrayKaySelector) { return self::ARROW_SIZES[$arraySelector][$arrayKaySelector] ?? ''; } /** * Get Line Style Arrow Parameters. * * @param string $arrowSelector * @param string $propertySelector * * @return string */ public function getLineStyleArrowParameters($arrowSelector, $propertySelector) { return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrowSelector]['size'], $propertySelector); } /** * Get Line Style Arrow Width. * * @param string $arrow * * @return string */ public function getLineStyleArrowWidth($arrow) { return $this->getLineStyleProperty(['arrow', $arrow, 'w']); } /** * Get Line Style Arrow Excel Length. * * @param string $arrow * * @return string */ public function getLineStyleArrowLength($arrow) { return $this->getLineStyleProperty(['arrow', $arrow, 'len']); } } phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php000064400000033161152427537250020267 0ustar00markerFillColor = 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. * * @return string */ public function getDataType() { 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($dataType) { 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). * * @return ?string */ public function getDataSource() { return $this->dataSource; } /** * Set Series Data Source (formula). * * @param ?string $dataSource * * @return $this */ public function setDataSource($dataSource) { $this->dataSource = $dataSource; return $this; } /** * Get Point Marker. * * @return string */ public function getPointMarker() { return $this->pointMarker; } /** * Set Point Marker. * * @param string $marker * * @return $this */ public function setPointMarker($marker) { $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) { $this->pointSize = $size; return $this; } /** * Get Series Format Code. * * @return string */ public function getFormatCode() { return $this->formatCode; } /** * Set Series Format Code. * * @param string $formatCode * * @return $this */ public function setFormatCode($formatCode) { $this->formatCode = $formatCode; return $this; } /** * Get Series Point Count. * * @return int */ public function getPointCount() { 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 (substr($fillString, 0, 1) === '*') { $type = 'schemeClr'; $value = substr($fillString, 1); } elseif (substr($fillString, 0, 1) === '/') { $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() { 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 DataSeriesValues */ public function setFillColor($color) { 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($color) { 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. * * @return null|float|int */ public function getLineWidth() { return $this->lineStyleProperties['width']; } /** * Set line width for the series. * * @param null|float|int $width * * @return $this */ public function setLineWidth($width) { $this->lineStyleProperties['width'] = $width; return $this; } /** * Identify if the Data Series is a multi-level or a simple series. * * @return null|bool */ public function isMultiLevelSeries() { if (!empty($this->dataValues)) { return is_array(array_values($this->dataValues)[0]); } return null; } /** * Return the level count of a multi-level Data Series. * * @return int */ public function multiLevelCount() { $levelCount = 0; foreach ($this->dataValues as $dataValueSet) { $levelCount = max($levelCount, count($dataValueSet)); } return $levelCount; } /** * Get Series Data Values. * * @return mixed[] */ public function getDataValues() { return $this->dataValues; } /** * Get the first Series Data value. * * @return mixed */ public function getDataValue() { $count = count($this->dataValues); if ($count == 0) { return null; } elseif ($count == 1) { return $this->dataValues[0]; } return $this->dataValues; } /** * Set Series Data Values. * * @param array $dataValues * * @return $this */ public function setDataValues($dataValues) { $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(/** @scrutinizer ignore-type */ $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. * * @var bool */ private $smoothLine; /** * Get Smooth Line. * * @return bool */ public function getSmoothLine() { return $this->smoothLine; } /** * Set Smooth Line. * * @param bool $smoothLine * * @return $this */ public function setSmoothLine($smoothLine) { $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; } } phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php000064400000011243152427537250016744 0ustar00setTrendLineProperties( $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/Chart/DataSeries.php000064400000021445152427537250017111 0ustar00plotType = $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 = $smoothLine; $this->plotStyle = $plotStyle; if ($plotDirection === null) { $plotDirection = self::DIRECTION_COL; } $this->plotDirection = $plotDirection; } /** * Get Plot Type. * * @return string */ public function getPlotType() { return $this->plotType; } /** * Set Plot Type. * * @param string $plotType * * @return $this */ public function setPlotType($plotType) { $this->plotType = $plotType; return $this; } /** * Get Plot Grouping Type. * * @return string */ public function getPlotGrouping() { return $this->plotGrouping; } /** * Set Plot Grouping Type. * * @param string $groupingType * * @return $this */ public function setPlotGrouping($groupingType) { $this->plotGrouping = $groupingType; return $this; } /** * Get Plot Direction. * * @return string */ public function getPlotDirection() { return $this->plotDirection; } /** * Set Plot Direction. * * @param string $plotDirection * * @return $this */ public function setPlotDirection($plotDirection) { $this->plotDirection = $plotDirection; return $this; } /** * Get Plot Order. * * @return int[] */ public function getPlotOrder() { return $this->plotOrder; } /** * Get Plot Labels. * * @return DataSeriesValues[] */ public function getPlotLabels() { return $this->plotLabel; } /** * Get Plot Label by Index. * * @param mixed $index * * @return DataSeriesValues|false */ public function getPlotLabelByIndex($index) { $keys = array_keys($this->plotLabel); if (in_array($index, $keys)) { return $this->plotLabel[$index]; } return false; } /** * Get Plot Categories. * * @return DataSeriesValues[] */ public function getPlotCategories() { return $this->plotCategory; } /** * Get Plot Category by Index. * * @param mixed $index * * @return DataSeriesValues|false */ public function getPlotCategoryByIndex($index) { $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. * * @return null|string */ public function getPlotStyle() { return $this->plotStyle; } /** * Set Plot Style. * * @param null|string $plotStyle * * @return $this */ public function setPlotStyle($plotStyle) { $this->plotStyle = $plotStyle; return $this; } /** * Get Plot Values. * * @return DataSeriesValues[] */ public function getPlotValues() { return $this->plotValues; } /** * Get Plot Values by Index. * * @param mixed $index * * @return DataSeriesValues|false */ public function getPlotValuesByIndex($index) { $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. * * @return int */ public function getPlotSeriesCount() { return count($this->plotValues); } /** * Get Smooth Line. * * @return bool */ public function getSmoothLine() { return $this->smoothLine; } /** * Set Smooth Line. * * @param bool $smoothLine * * @return $this */ public function setSmoothLine($smoothLine) { $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); } } } } phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php000064400000007561152427537250016577 0ustar00layout = $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. * * @return int */ public function getPlotSeriesCount() { $seriesCount = 0; foreach ($this->plotSeries as $plot) { $seriesCount += $plot->getPlotSeriesCount(); } return $seriesCount; } /** * Get Plot Series. * * @return DataSeries[] */ public function getPlotGroup() { return $this->plotSeries; } /** * Get Plot Series by Index. * * @param mixed $index * * @return DataSeries */ public function getPlotGroupByIndex($index) { return $this->plotSeries[$index]; } /** * Set Plot Series. * * @param DataSeries[] $plotSeries * * @return $this */ public function setPlotSeries(array $plotSeries) { $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. * * @return array */ public function getGradientFillStops() { return $this->gradientFillStops; } /** @var ?int */ private $gapWidth; /** @var bool */ private $useUpBars = false; /** @var bool */ private $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; } } phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php000064400000026334152427537250016344 0ustar00layoutTarget = $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. * * @return ?string */ public function getLayoutTarget() { return $this->layoutTarget; } /** * Set Layout Target. * * @param ?string $target * * @return $this */ public function setLayoutTarget($target) { $this->layoutTarget = $target; return $this; } /** * Get X-Mode. * * @return ?string */ public function getXMode() { return $this->xMode; } /** * Set X-Mode. * * @param ?string $mode * * @return $this */ public function setXMode($mode) { $this->xMode = (string) $mode; return $this; } /** * Get Y-Mode. * * @return ?string */ public function getYMode() { return $this->yMode; } /** * Set Y-Mode. * * @param ?string $mode * * @return $this */ public function setYMode($mode) { $this->yMode = (string) $mode; return $this; } /** * Get X-Position. * * @return null|float|int */ public function getXPosition() { return $this->xPos; } /** * Set X-Position. * * @param ?float $position * * @return $this */ public function setXPosition($position) { $this->xPos = (float) $position; return $this; } /** * Get Y-Position. * * @return null|float */ public function getYPosition() { return $this->yPos; } /** * Set Y-Position. * * @param ?float $position * * @return $this */ public function setYPosition($position) { $this->yPos = (float) $position; return $this; } /** * Get Width. * * @return ?float */ public function getWidth() { return $this->width; } /** * Set Width. * * @param ?float $width * * @return $this */ public function setWidth($width) { $this->width = $width; return $this; } /** * Get Height. * * @return null|float */ public function getHeight() { return $this->height; } /** * Set Height. * * @param ?float $height * * @return $this */ public function setHeight($height) { $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; } } phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php000064400000002050152427537250016625 0ustar00font = 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; } } phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php000064400000037536152427537250016136 0ustar00name = $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(); } /** * Get Name. * * @return string */ public function getName() { 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) { $this->worksheet = $worksheet; return $this; } public function getTitle(): ?Title { return $this->title; } /** * Set Title. * * @return $this */ public function setTitle(Title $title) { $this->title = $title; return $this; } public function getLegend(): ?Legend { return $this->legend; } /** * Set Legend. * * @return $this */ public function setLegend(Legend $legend) { $this->legend = $legend; return $this; } public function getXAxisLabel(): ?Title { return $this->xAxisLabel; } /** * Set X-Axis Label. * * @return $this */ public function setXAxisLabel(Title $label) { $this->xAxisLabel = $label; return $this; } public function getYAxisLabel(): ?Title { return $this->yAxisLabel; } /** * Set Y-Axis Label. * * @return $this */ public function setYAxisLabel(Title $label) { $this->yAxisLabel = $label; return $this; } public function getPlotArea(): ?PlotArea { return $this->plotArea; } /** * Set Plot Area. */ public function setPlotArea(PlotArea $plotArea): self { $this->plotArea = $plotArea; return $this; } /** * Get Plot Visible Only. * * @return bool */ public function getPlotVisibleOnly() { return $this->plotVisibleOnly; } /** * Set Plot Visible Only. * * @param bool $plotVisibleOnly * * @return $this */ public function setPlotVisibleOnly($plotVisibleOnly) { $this->plotVisibleOnly = $plotVisibleOnly; return $this; } /** * Get Display Blanks as. * * @return string */ public function getDisplayBlanksAs() { return $this->displayBlanksAs; } /** * Set Display Blanks as. * * @param string $displayBlanksAs * * @return $this */ public function setDisplayBlanksAs($displayBlanksAs) { $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; } /** * Get Major Gridlines. * * @deprecated 1.24.0 Use Axis->getMajorGridlines() * @see Axis::getMajorGridlines() * * @codeCoverageIgnore */ public function getMajorGridlines(): ?GridLines { return $this->yAxis->getMajorGridLines(); } /** * Get Minor Gridlines. * * @deprecated 1.24.0 Use Axis->getMinorGridlines() * @see Axis::getMinorGridlines() * * @codeCoverageIgnore */ public function getMinorGridlines(): ?GridLines { return $this->yAxis->getMinorGridLines(); } /** * Set the Top Left position for the chart. * * @param string $cellAddress * @param int $xOffset * @param int $yOffset * * @return $this */ public function setTopLeftPosition($cellAddress, $xOffset = null, $yOffset = null) { $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. * * @return string */ public function getTopLeftCell() { return $this->topLeftCellRef; } /** * Set the Top Left cell position for the chart. * * @param string $cellAddress * * @return $this */ public function setTopLeftCell($cellAddress) { $this->topLeftCellRef = $cellAddress; return $this; } /** * Set the offset position within the Top Left cell for the chart. * * @param ?int $xOffset * @param ?int $yOffset * * @return $this */ public function setTopLeftOffset($xOffset, $yOffset) { 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() { return [ 'X' => $this->topLeftXOffset, 'Y' => $this->topLeftYOffset, ]; } /** * @param int $xOffset * * @return $this */ public function setTopLeftXOffset($xOffset) { $this->topLeftXOffset = $xOffset; return $this; } public function getTopLeftXOffset(): int { return $this->topLeftXOffset; } /** * @param int $yOffset * * @return $this */ public function setTopLeftYOffset($yOffset) { $this->topLeftYOffset = $yOffset; return $this; } public function getTopLeftYOffset(): int { return $this->topLeftYOffset; } /** * Set the Bottom Right position of the chart. * * @param string $cellAddress * @param int $xOffset * @param int $yOffset * * @return $this */ public function setBottomRightPosition($cellAddress = '', $xOffset = null, $yOffset = null) { $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() { 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 = '') { $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. * * @param ?int $xOffset * @param ?int $yOffset * * @return $this */ public function setBottomRightOffset($xOffset, $yOffset) { 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() { return [ 'X' => $this->bottomRightXOffset, 'Y' => $this->bottomRightYOffset, ]; } /** * @param int $xOffset * * @return $this */ public function setBottomRightXOffset($xOffset) { $this->bottomRightXOffset = $xOffset; return $this; } public function getBottomRightXOffset(): int { return $this->bottomRightXOffset; } /** * @param int $yOffset * * @return $this */ public function setBottomRightYOffset($yOffset) { $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($outputDestination = null) { 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); // @phpstan-ignore-line } 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; } } phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php000064400000000267152427537250016744 0ustar00caption = $caption; $this->layout = $layout; $this->setOverlay($overlay); } /** * Get caption. * * @return array|RichText|string */ public function getCaption() { return $this->caption; } public function getCaptionText(): string { $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 = $textx; if ($text instanceof RichText) { $retVal .= $text->getPlainText(); } else { $retVal .= $text; } } return $retVal; } /** * Set caption. * * @param array|RichText|string $caption * * @return $this */ public function setCaption($caption) { $this->caption = $caption; return $this; } /** * Get allow overlay of other elements? * * @return bool */ public function getOverlay() { return $this->overlay; } /** * Set allow overlay of other elements? * * @param bool $overlay */ public function setOverlay($overlay): void { $this->overlay = $overlay; } public function getLayout(): ?Layout { return $this->layout; } } phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php000064400000010406152427537250016256 0ustar00 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. * * @var string */ private $position = self::POSITION_RIGHT; /** * Allow overlay of other elements? * * @var bool */ private $overlay = true; /** * Legend Layout. * * @var ?Layout */ private $layout; /** @var GridLines */ private $borderLines; /** @var ChartColor */ private $fillColor; /** @var ?AxisText */ private $legendText; /** * Create a new Legend. * * @param string $position * @param ?Layout $layout * @param bool $overlay */ public function __construct($position = self::POSITION_RIGHT, ?Layout $layout = null, $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. * * @return string */ public function getPosition() { return $this->position; } /** * Get legend position using an excel string value. * * @param string $position see self::POSITION_* * * @return bool */ public function setPosition($position) { if (!in_array($position, self::POSITION_XLREF)) { return false; } $this->position = $position; return true; } /** * Get legend position as an Excel internal numeric value. * * @return false|int */ public function getPositionXL() { // Scrutinizer thinks the following could return string. It is wrong. 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_* * * @return bool */ public function setPositionXL($positionXL) { if (!isset(self::POSITION_XLREF[$positionXL])) { return false; } $this->position = self::POSITION_XLREF[$positionXL]; return true; } /** * Get allow overlay of other elements? * * @return bool */ public function getOverlay() { return $this->overlay; } /** * Set allow overlay of other elements? * * @param bool $overlay */ public function setOverlay($overlay): void { $this->overlay = $overlay; } /** * Get Layout. * * @return ?Layout */ public function getLayout() { 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; } } phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php000064400000000252152427537250017014 0ustar00setColorPropertiesArray($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; } /** * @param null|float|int|string $alpha * @param null|float|int|string $brightness */ public function setColorProperties(?string $color, $alpha = null, ?string $type = null, $brightness = null): self { if (empty($type) && !empty($color)) { if (substr($color, 0, 1) === '*') { $type = 'schemeClr'; $color = substr($color, 1); } elseif (substr($color, 0, 1) === '/') { $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. * * @param string $propertyName * * @return null|int|string */ public function getColorProperty($propertyName) { $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'; } /** * @param float|int|string $alpha */ public static function alphaFromXml($alpha): int { return 100 - ((int) $alpha / 1000); } } phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php000064400000020373152427537250015770 0ustar00fillColor = new ChartColor(); } /** * Chart Major Gridlines as. * * @var ?GridLines */ private $majorGridlines; /** * Chart Minor Gridlines as. * * @var ?GridLines */ private $minorGridlines; /** * Axis Number. * * @var mixed[] */ private $axisNumber = [ 'format' => self::FORMAT_CODE_GENERAL, 'source_linked' => 1, 'numeric' => null, ]; /** @var string */ private $axisType = ''; /** @var ?AxisText */ private $axisText; /** * Axis Options. * * @var mixed[] */ private $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, ]; /** * Fill Properties. * * @var ChartColor */ private $fillColor; private const NUMERIC_FORMAT = [ Properties::FORMAT_CODE_NUMBER, Properties::FORMAT_CODE_DATE, Properties::FORMAT_CODE_DATE_ISO8601, ]; /** @var bool */ private $noFill = false; /** * Get Series Data Type. * * @param mixed $format_code */ public function setAxisNumberProperties($format_code, ?bool $numeric = null, int $sourceLinked = 0): void { $format = (string) $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. * * @return string */ public function getAxisNumberFormat() { return $this->axisNumber['format']; } /** * Get Axis Number Source Linked. * * @return string */ public function getAxisNumberSourceLinked() { 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, ?string $value): void { if ($value !== null && $value !== '') { $this->axisOptions[$key] = $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, ?string $minimum = null, ?string $maximum = null, ?string $majorUnit = null, ?string $minorUnit = null, ?string $textRotation = null, ?string $hidden = null, ?string $baseTimeUnit = null, ?string $majorTimeUnit = null, ?string $minorTimeUnit = 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); } /** * Get Axis Options Property. * * @param string $property * * @return ?string */ public function getAxisOptionsProperty($property) { 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. * * @param string $orientation */ public function setAxisOrientation($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. * * @param ?string $color * @param ?int $alpha * @param ?string $AlphaType */ public function setFillParameters($color, $alpha = null, $AlphaType = ChartColor::EXCEL_COLOR_TYPE_RGB): void { $this->fillColor->setColorProperties($color, $alpha, $AlphaType); } /** * Get Fill Property. * * @param string $property * * @return string */ public function getFillProperty($property) { return (string) $this->fillColor->getColorProperty($property); } public function getFillColorObject(): ChartColor { return $this->fillColor; } /** * Get Line Color Property. * * @deprecated 1.24.0 * Use the getLineColor property in the Properties class instead * @see Properties::getLineColorProperty() * * @param string $propertyName * * @return null|int|string */ public function getLineProperty($propertyName) { return $this->getLineColorProperty($propertyName); } /** @var string */ private $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; } } phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Currency.php000064400000010442152427537250022552 0ustar00setCurrencyCode($currencyCode); $this->setThousandsSeparator($thousandsSeparator); $this->setDecimals($decimals); $this->setCurrencySymbolPosition($currencySymbolPosition); $this->setCurrencySymbolSpacing($currencySymbolSpacing); $this->setLocale($locale); } public function setCurrencyCode(string $currencyCode): void { $this->currencyCode = $currencyCode; } public function setCurrencySymbolPosition(bool $currencySymbolPosition = self::LEADING_SYMBOL): void { $this->currencySymbolPosition = $currencySymbolPosition; } public function setCurrencySymbolSpacing(bool $currencySymbolSpacing = self::SYMBOL_WITHOUT_SPACING): void { $this->currencySymbolSpacing = $currencySymbolSpacing; } protected function getLocaleFormat(): string { $formatter = new Locale($this->fullLocale, NumberFormatter::CURRENCY); $mask = $formatter->format(); if ($this->decimals === 0) { $mask = (string) preg_replace('/\.0+/miu', '', $mask); } return str_replace('¤', $this->formatCurrencyCode(), $mask); } private function formatCurrencyCode(): string { if ($this->locale === null) { return $this->currencyCode; } return "[\${$this->currencyCode}-{$this->locale}]"; } public function format(): string { if ($this->localeFormat !== null) { return $this->localeFormat; } return sprintf( '%s%s%s0%s%s%s', $this->currencySymbolPosition === self::LEADING_SYMBOL ? $this->formatCurrencyCode() : null, ( $this->currencySymbolPosition === self::LEADING_SYMBOL && $this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING ) ? "\u{a0}" : '', $this->thousandsSeparator ? '#,##' : null, $this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null, ( $this->currencySymbolPosition === self::TRAILING_SYMBOL && $this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING ) ? "\u{a0}" : '', $this->currencySymbolPosition === self::TRAILING_SYMBOL ? $this->formatCurrencyCode() : null ); } } phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php000064400000005711152427537250021661 0ustar00separators = $this->padSeparatorArray( is_array($separators) ? $separators : [$separators], count($formatBlocks) - 1 ); $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks); } private function mapFormatBlocks(string $value): string { // Any date masking codes are returned as lower case values // except for AM/PM, which is set to uppercase if (in_array(mb_strtolower($value), self::TIME_BLOCKS, true)) { return mb_strtolower($value); } elseif (mb_strtoupper($value) === self::MORNING_AFTERNOON) { return mb_strtoupper($value); } // Wrap any string literals in quotes, so that they're clearly defined as string literals return $this->wrapLiteral($value); } public function format(): string { return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators)); } } phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php000064400000006704152427537250021643 0ustar00separators = $this->padSeparatorArray( is_array($separators) ? $separators : [$separators], count($formatBlocks) - 1 ); $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks); } private function mapFormatBlocks(string $value): string { // Any date masking codes are returned as lower case values if (in_array(mb_strtolower($value), self::DATE_BLOCKS, true)) { return mb_strtolower($value); } // Wrap any string literals in quotes, so that they're clearly defined as string literals return $this->wrapLiteral($value); } public function format(): string { return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators)); } } phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.php000064400000002116152427537250022156 0ustar00[a-z]{2})([-_](?P